Exit Full View

Games Cupboard / gamescupboard-server / src / main / kotlin / uk / co / nickthecoder / gamescupboard / server / games / Chess.kt

package uk.co.nickthecoder.gamescupboard.server.games

import uk.co.nickthecoder.gamescupboard.common.*
import uk.co.nickthecoder.gamescupboard.server.*

private const val snapX = 49
private const val snapY = 49
private const val left = 400 - snapX * 4
private const val top = 300 - snapY * 4
private const val bottom = top + 8 * snapY

private val boardArea = SpecialArea(
    "chessBoard", AreaType.PUBLIC,
    left,
    top,
    8 * snapX,
    8 * snapY,
    changeZOrder = ChangeZOrder.BOTTOM,
    snap = RectangularSnapToGrid(snapX, snapY)
)

private val chessPieces = Grid("chessPieces", "chessPieces.png", 8, 2, 46, 50)

private fun chessPieces(startIndex: Int = 0): List<GameObject> {
    val result = mutableListOf<GameObject>()
    var id = startIndex
    // Pawns
    for (tx in 0..7) {
        val x = left + snapX / 2 + tx * snapX
        val pieceGX = listOf(2, 4, 3, 1, 0, 3, 4, 2)[tx]
        // Back pawns
        result.add(GridImageObject(id++, x = x, y = top + snapY + snapY / 2, grid = chessPieces, gx = 5, gy = 1))
        // White pawns
        result.add(GridImageObject(id++, x = x, y = bottom - snapY - snapY / 2, grid = chessPieces, gx = 5, gy = 0))
        // Black pieces
        result.add(GridImageObject(id++, x = x, y = top + snapY / 2, grid = chessPieces, gx = pieceGX, gy = 1))
        // White pieces
        result.add(GridImageObject(id++, x = x, y = bottom - snapY / 2, grid = chessPieces, gx = pieceGX, gy = 0))
    }
    return result
}

private val regularChess = GameVariation(
    "chess",
    "Chess",
    minPlayers = 2, // For hand and brain, the "brain" should join as spectators (enable spec chat!)
    maxPlayers = 2,
    grids = listOf(chessPieces),
    backgroundObjects = listOf(
        ImageObject(-1, 400, 300, path = "backgrounds/khaki.jpg", draggable = false),
        ImageObject(-2, 400, 300, path = "chessBoard.jpg", draggable = false),
    ),
    playingObjects = chessPieces(0),
    specialAreas = listOf(boardArea),
    specialPoints = listOf(),
    avatarPositions = VerticalAvatarPositions(70, 3),
    mirrorX = playingAreaWidth/2,
    mirrorY = playingAreaHeight/2,
    playerColors = listOf(GameVariation.chessBlack, GameVariation.chessWhite)
)

private val seirawanChess = GameVariation(
    "seirawan",
    "Seirawan Chess",
    minPlayers = 2, // For hand and brain, the "brain" should join as spectators (enable spec chat!)
    maxPlayers = 2,
    grids = regularChess.grids,
    backgroundObjects = regularChess.backgroundObjects,
    playingObjects = regularChess.playingObjects + listOf(
        GridImageObject(regularChess.playingObjects.size, 350, 30, grid = chessPieces, gx = 6, gy = 1),
        GridImageObject(regularChess.playingObjects.size + 1, 450, 30, grid = chessPieces, gx = 7, gy = 1),
        GridImageObject(regularChess.playingObjects.size + 2, 350, 570, grid = chessPieces, gx = 6, gy = 0),
        GridImageObject(regularChess.playingObjects.size + 3, 450, 570, grid = chessPieces, gx = 7, gy = 0)
    ),
    specialAreas = regularChess.specialAreas,
    specialPoints = listOf(),
    avatarPositions = regularChess.avatarPositions,
    mirrorX = regularChess.mirrorX,
    mirrorY = regularChess.mirrorY,
    playerColors = regularChess.playerColors,
    rules = """        
        The normal rules of chess apply. However, when you first move each piece
        (`Rook`, `Knight`, `Bishop`, `Queen` or `King`), you may additionally place the `Hawk` or `Elephant`
        in the newly vacated square.
        
        If you move all of your pieces without placing either (or both) of the extra pieces,
        you lose that right.
        
        _The Elephant_
        
        Moves as a `Rook` or a `Knight`.        
        Also known as a `Chancellor` or `Empress`.
        
        _The Hawk_
        
        Moves as a `Bishop` or `Knight`.
        Also known as an `Archbishop` or `Princess`.
        
        Read about [Seirawan Chess on Wikipedia|https://en.wikipedia.org/wiki/Capablanca_chess#Seirawan_chess].

        _Similar Variations_
        
        `Seirawan Chess` is similar to [Capablanca Chess|https://en.wikipedia.org/wiki/Capablanca_chess]`.
        Capablanca Chess is played on a 10x8 board,
        with no pieces in hand. The `Archbishop` and `Chancellor` are situated next to the rooks.
        
        `Birds Chess` is the same a `Capablanca`, but with the `Archbishop` and `Chancellor` placed next to
        the `King` and `Queen`.
                """.trimIndent()
)

private val duckChess = GameVariation(
    "duckChess",
    "Duck Chess",
    minPlayers = 2, // For hand and brain, the "brain" should join as spectators (enable spec chat!)
    maxPlayers = 2,
    grids = regularChess.grids,
    backgroundObjects = regularChess.backgroundObjects,
    playingObjects = regularChess.playingObjects + listOf(
        ImageObject(regularChess.playingObjects.size, 100, 300, path = "chessDuck.png")
    ),
    specialAreas = regularChess.specialAreas,
    specialPoints = regularChess.specialPoints,
    avatarPositions = regularChess.avatarPositions,
    mirrorX = regularChess.mirrorX,
    mirrorY = regularChess.mirrorY,
    playerColors = regularChess.playerColors,
    rules = """
        A move consists of two parts. First move a `regular` chess piece, then move the `duck`.
        It is illegal to move the duck before moving a `regular` piece. It's a `fowl` move!
        
        The `duck` can be placed on any unoccupied square (but it _must_ be moved).
        
        Regular chess pieces cannot move through the `duck`, or land on the same square as the `duck`.
        As with regular pieces, the Knight can jump over (or around) the `duck`.
        
        The rules of `Check` and `Checkmate` do not apply. You can move your king into check.
        You can castle through check.
        
        To win the game, take your opponent's King.
        In the event of `stalemate`, the player unable to move is the victor.
        
        _Quacktics_
        
        In the early game, position the `duck` to prevent your opponent developing.
        e.g. block the `Pawn` that opens up a `Bishop`.
        
        Use the `duck` to block your opponent's `King`.
        If your `King` has only 1 escape square, beware of `smothered duck mate`.
        
        Place the `duck` where you do _not_ want it on your next turn. Your opponent is forced to move it elsewhere.
        
        Long range pieces (`Queen`, `Rook`, `Bishop`) do not defend well, because the duck can be placed between them
        and the piece they are defending.
        Therefore the `Knight` may be stronger than the `Bishop`.
        
        Build up multiple threats; the `duck` can't defend again all of them!
        
        Watch Eric Rosen's journey from `novice` to `grand duck wrangler` on
        [YouTube|https://www.youtube.com/watch?v=BqvYsPAufB8&list=PLdT3OotRiHJnOXq9TcNJh_xfOvBXncNcP]
        
        """.trimIndent()
)

private val draughtsPieces = Grid("draughtsPieces", "draughtsPieces.png", 4, 1, 46, 46)

private val redPoint = SpecialPoint("redCounters", (boardArea.x + boardArea.width + playingAreaWidth) / 2, 200)
private val bluePoint = SpecialPoint("redCounters", (boardArea.x + boardArea.width + playingAreaWidth) / 2, 400)

private fun draughtsPieces(startIndex: Int = 0): List<GameObject> {
    val result = mutableListOf<GameObject>()
    var id = startIndex
    for (y in 0..2) {
        val start = if (y == 1) 0 else 1
        val end = if (y == 1) 6 else 7
        for (x in (start..end).step(2)) {
            result.add(
                // Red pieces
                FlippableImageObject(
                    id++,
                    boardArea.x + x * snapX + snapX / 2,
                    boardArea.y + y * snapY + snapY / 2,
                    grid = draughtsPieces, gx = 0, gy = 0, altX = 1, isFaceUp = true
                )
            )
            result.add(
                // Blue pieces
                FlippableImageObject(
                    id++,
                    boardArea.x + boardArea.width - x * snapX - snapX / 2,
                    boardArea.y + boardArea.height - y * snapY - snapY / 2,
                    grid = draughtsPieces, gx = 2, gy = 0, altX = 3, isFaceUp = true
                )
            )
        }
        // Spare Red pieces
        for (i in 0..1) {
            result.add(
                FlippableImageObject(
                    id++,
                    redPoint.x,
                    redPoint.y,
                    grid = draughtsPieces, gx = 0, gy = 0, altX = 1, isFaceUp = true
                ).apply { regen = true }
            )
        }
        // Spare Blue pieces
        for (i in 0..1) {
            result.add(
                FlippableImageObject(
                    id++,
                    bluePoint.x,
                    bluePoint.y,
                    grid = draughtsPieces, gx = 2, gy = 0, altX = 3, isFaceUp = true
                ).apply { regen = true }
            )
        }

    }
    return result
}

private val draughts = GameVariation(
    "draughts",
    "Draughts",
    minPlayers = 2, // For hand and brain, the "brain" should join as spectators (enable spec chat!)
    maxPlayers = 2,
    grids = listOf(draughtsPieces),
    backgroundObjects = regularChess.backgroundObjects + listOf(
        TextObject(-3, 400, 560, text = "Double-Click a piece to create a King")
    ),
    playingObjects = draughtsPieces(1),
    specialAreas = regularChess.specialAreas,
    specialPoints = listOf(redPoint, bluePoint),
    avatarPositions = regularChess.avatarPositions,
    mirrorX = regularChess.mirrorX,
    mirrorY = regularChess.mirrorY
)

val chess = GameType(
    "chessAndDraughts", "Chess & Draughts", "chessBoard.jpg",
    listOf(regularChess, duckChess, seirawanChess, draughts)
)