chore: Set up shared-models library and initial project structure for NowChessSystems
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
package de.nowchess.api.board
|
||||
|
||||
enum Color:
|
||||
case White, Black
|
||||
|
||||
def opposite: Color = this match
|
||||
case White => Black
|
||||
case Black => White
|
||||
@@ -0,0 +1,20 @@
|
||||
package de.nowchess.api.board
|
||||
|
||||
/** A chess piece on the board — a combination of a color and a piece type. */
|
||||
final case class Piece(color: Color, pieceType: PieceType)
|
||||
|
||||
object Piece:
|
||||
// Convenience constructors
|
||||
val WhitePawn: Piece = Piece(Color.White, PieceType.Pawn)
|
||||
val WhiteKnight: Piece = Piece(Color.White, PieceType.Knight)
|
||||
val WhiteBishop: Piece = Piece(Color.White, PieceType.Bishop)
|
||||
val WhiteRook: Piece = Piece(Color.White, PieceType.Rook)
|
||||
val WhiteQueen: Piece = Piece(Color.White, PieceType.Queen)
|
||||
val WhiteKing: Piece = Piece(Color.White, PieceType.King)
|
||||
|
||||
val BlackPawn: Piece = Piece(Color.Black, PieceType.Pawn)
|
||||
val BlackKnight: Piece = Piece(Color.Black, PieceType.Knight)
|
||||
val BlackBishop: Piece = Piece(Color.Black, PieceType.Bishop)
|
||||
val BlackRook: Piece = Piece(Color.Black, PieceType.Rook)
|
||||
val BlackQueen: Piece = Piece(Color.Black, PieceType.Queen)
|
||||
val BlackKing: Piece = Piece(Color.Black, PieceType.King)
|
||||
@@ -0,0 +1,4 @@
|
||||
package de.nowchess.api.board
|
||||
|
||||
enum PieceType:
|
||||
case Pawn, Knight, Bishop, Rook, Queen, King
|
||||
@@ -0,0 +1,41 @@
|
||||
package de.nowchess.api.board
|
||||
|
||||
/**
|
||||
* A file (column) on the chess board, a–h.
|
||||
* Ordinal values 0–7 correspond to a–h.
|
||||
*/
|
||||
enum File:
|
||||
case A, B, C, D, E, F, G, H
|
||||
|
||||
/**
|
||||
* A rank (row) on the chess board, 1–8.
|
||||
* Ordinal values 0–7 correspond to ranks 1–8.
|
||||
*/
|
||||
enum Rank:
|
||||
case R1, R2, R3, R4, R5, R6, R7, R8
|
||||
|
||||
/**
|
||||
* A unique square on the board, identified by its file and rank.
|
||||
*
|
||||
* @param file the column, a–h
|
||||
* @param rank the row, 1–8
|
||||
*/
|
||||
final case class Square(file: File, rank: Rank):
|
||||
/** Algebraic notation string, e.g. "e4". */
|
||||
override def toString: String =
|
||||
s"${file.toString.toLowerCase}${rank.ordinal + 1}"
|
||||
|
||||
object Square:
|
||||
/** Parse a square from algebraic notation (e.g. "e4").
|
||||
* Returns None if the input is not a valid square name. */
|
||||
def fromAlgebraic(s: String): Option[Square] =
|
||||
if s.length != 2 then None
|
||||
else
|
||||
val fileChar = s.charAt(0)
|
||||
val rankChar = s.charAt(1)
|
||||
val fileOpt = File.values.find(_.toString.equalsIgnoreCase(fileChar.toString))
|
||||
val rankOpt =
|
||||
rankChar.toString.toIntOption.flatMap(n =>
|
||||
if n >= 1 && n <= 8 then Some(Rank.values(n - 1)) else None
|
||||
)
|
||||
for f <- fileOpt; r <- rankOpt yield Square(f, r)
|
||||
@@ -0,0 +1,67 @@
|
||||
package de.nowchess.api.game
|
||||
|
||||
import de.nowchess.api.board.{Color, Square}
|
||||
|
||||
/**
|
||||
* Castling availability flags for one side.
|
||||
*
|
||||
* @param kingSide king-side castling still legally available
|
||||
* @param queenSide queen-side castling still legally available
|
||||
*/
|
||||
final case class CastlingRights(kingSide: Boolean, queenSide: Boolean)
|
||||
|
||||
object CastlingRights:
|
||||
val None: CastlingRights = CastlingRights(kingSide = false, queenSide = false)
|
||||
val Both: CastlingRights = CastlingRights(kingSide = true, queenSide = true)
|
||||
|
||||
/** Outcome of a finished game. */
|
||||
enum GameResult:
|
||||
case WhiteWins
|
||||
case BlackWins
|
||||
case Draw
|
||||
|
||||
/** Lifecycle state of a game. */
|
||||
enum GameStatus:
|
||||
case NotStarted
|
||||
case InProgress
|
||||
case Finished(result: GameResult)
|
||||
|
||||
/**
|
||||
* A FEN-compatible snapshot of board and game state.
|
||||
*
|
||||
* The board is represented as a FEN piece-placement string (rank 8 to rank 1,
|
||||
* separated by '/'). All other fields mirror standard FEN fields.
|
||||
*
|
||||
* @param piecePlacement FEN piece-placement field, e.g.
|
||||
* "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR"
|
||||
* @param activeColor side to move
|
||||
* @param castlingWhite castling rights for White
|
||||
* @param castlingBlack castling rights for Black
|
||||
* @param enPassantTarget square behind the double-pushed pawn, if any
|
||||
* @param halfMoveClock plies since last capture or pawn advance (50-move rule)
|
||||
* @param fullMoveNumber increments after Black's move, starts at 1
|
||||
* @param status current lifecycle status of the game
|
||||
*/
|
||||
final case class GameState(
|
||||
piecePlacement: String,
|
||||
activeColor: Color,
|
||||
castlingWhite: CastlingRights,
|
||||
castlingBlack: CastlingRights,
|
||||
enPassantTarget: Option[Square],
|
||||
halfMoveClock: Int,
|
||||
fullMoveNumber: Int,
|
||||
status: GameStatus
|
||||
)
|
||||
|
||||
object GameState:
|
||||
/** Standard starting position. */
|
||||
val initial: GameState = GameState(
|
||||
piecePlacement = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR",
|
||||
activeColor = Color.White,
|
||||
castlingWhite = CastlingRights.Both,
|
||||
castlingBlack = CastlingRights.Both,
|
||||
enPassantTarget = None,
|
||||
halfMoveClock = 0,
|
||||
fullMoveNumber = 1,
|
||||
status = GameStatus.InProgress
|
||||
)
|
||||
@@ -0,0 +1,33 @@
|
||||
package de.nowchess.api.move
|
||||
|
||||
import de.nowchess.api.board.{PieceType, Square}
|
||||
|
||||
/** The piece a pawn may be promoted to (all non-pawn, non-king pieces). */
|
||||
enum PromotionPiece:
|
||||
case Knight, Bishop, Rook, Queen
|
||||
|
||||
/** Classifies special move semantics beyond a plain quiet move or capture. */
|
||||
enum MoveType:
|
||||
/** A normal move or capture with no special rule. */
|
||||
case Normal
|
||||
/** Kingside castling (O-O). */
|
||||
case CastleKingside
|
||||
/** Queenside castling (O-O-O). */
|
||||
case CastleQueenside
|
||||
/** En-passant pawn capture. */
|
||||
case EnPassant
|
||||
/** Pawn promotion; carries the chosen promotion piece. */
|
||||
case Promotion(piece: PromotionPiece)
|
||||
|
||||
/**
|
||||
* A half-move (ply) in a chess game.
|
||||
*
|
||||
* @param from origin square
|
||||
* @param to destination square
|
||||
* @param moveType special semantics; defaults to Normal
|
||||
*/
|
||||
final case class Move(
|
||||
from: Square,
|
||||
to: Square,
|
||||
moveType: MoveType = MoveType.Normal
|
||||
)
|
||||
@@ -0,0 +1,27 @@
|
||||
package de.nowchess.api.player
|
||||
|
||||
/**
|
||||
* An opaque player identifier.
|
||||
*
|
||||
* Wraps a plain String so that IDs are not accidentally interchanged with
|
||||
* other String values at compile time.
|
||||
*/
|
||||
opaque type PlayerId = String
|
||||
|
||||
object PlayerId:
|
||||
def apply(value: String): PlayerId = value
|
||||
extension (id: PlayerId) def value: String = id
|
||||
|
||||
/**
|
||||
* The minimal cross-service identity stub for a player.
|
||||
*
|
||||
* Full profile data (email, rating history, etc.) lives in the user-management
|
||||
* service. Only what every service needs is held here.
|
||||
*
|
||||
* @param id unique identifier
|
||||
* @param displayName human-readable name shown in the UI
|
||||
*/
|
||||
final case class PlayerInfo(
|
||||
id: PlayerId,
|
||||
displayName: String
|
||||
)
|
||||
@@ -0,0 +1,62 @@
|
||||
package de.nowchess.api.response
|
||||
|
||||
/**
|
||||
* A standardised envelope for every API response.
|
||||
*
|
||||
* Success and failure are modelled as subtypes so that callers
|
||||
* can pattern-match exhaustively.
|
||||
*
|
||||
* @tparam A the payload type for a successful response
|
||||
*/
|
||||
sealed trait ApiResponse[+A]
|
||||
|
||||
object ApiResponse:
|
||||
/** A successful response carrying a payload. */
|
||||
final case class Success[A](data: A) extends ApiResponse[A]
|
||||
|
||||
/** A failed response carrying one or more errors. */
|
||||
final case class Failure(errors: List[ApiError]) extends ApiResponse[Nothing]
|
||||
|
||||
/** Convenience constructor for a single-error failure. */
|
||||
def error(err: ApiError): Failure = Failure(List(err))
|
||||
|
||||
/**
|
||||
* A structured error descriptor.
|
||||
*
|
||||
* @param code machine-readable error code (e.g. "INVALID_MOVE", "NOT_FOUND")
|
||||
* @param message human-readable explanation
|
||||
* @param field optional field name when the error relates to a specific input
|
||||
*/
|
||||
final case class ApiError(
|
||||
code: String,
|
||||
message: String,
|
||||
field: Option[String] = None
|
||||
)
|
||||
|
||||
/**
|
||||
* Pagination metadata for list responses.
|
||||
*
|
||||
* @param page current 0-based page index
|
||||
* @param pageSize number of items per page
|
||||
* @param totalItems total number of items across all pages
|
||||
*/
|
||||
final case class Pagination(
|
||||
page: Int,
|
||||
pageSize: Int,
|
||||
totalItems: Long
|
||||
):
|
||||
def totalPages: Int =
|
||||
if pageSize <= 0 then 0
|
||||
else Math.ceil(totalItems.toDouble / pageSize).toInt
|
||||
|
||||
/**
|
||||
* A paginated list response envelope.
|
||||
*
|
||||
* @param items the items on the current page
|
||||
* @param pagination pagination metadata
|
||||
* @tparam A the item type
|
||||
*/
|
||||
final case class PagedResponse[A](
|
||||
items: List[A],
|
||||
pagination: Pagination
|
||||
)
|
||||
Reference in New Issue
Block a user