package uk.co.nickthecoder.gamescupboard.server.games
import FaceUpOrDown
import MoveObject
import uk.co.nickthecoder.gamescupboard.common.*
import uk.co.nickthecoder.gamescupboard.server.*
import uk.co.nickthecoder.gamescupboard.server.commands.*
import kotlin.random.Random
private val cardsGrid = Grid("cards", "cards.png", 13, 5, 41, 61)
private val includeJokers = VariationOption("includeJokers", "Include Jokers") {
it.extraPlayingObjects(jokers(it.playingObjects.size))
}
private val extraPack = VariationOption("extraPack", "Extra Pack of Cards") {
it.extraPlayingObjects(packOfCards(startIndex = it.playingObjects.size))
}
private val extraPackWithJokers = VariationOption("extraPackWithJokers", "Extra Pack of Cards with Jokers") {
it.extraPlayingObjects(packOfCards(startIndex = it.playingObjects.size))
.extraPlayingObjects(jokers(it.playingObjects.size))
}
private fun packOfCards(
startIndex: Int = 0, packs: Int = 1, x: Int = deckPoint.x, y: Int = deckPoint.y,
isFaceUp: Boolean = false, includeJokers: Boolean = false
): List<GameObject> {
val result = mutableListOf<GameObject>()
var id = startIndex
for (pack in 0 until packs) {
for (gridX in 0..12) {
for (gridY in 0..3) {
result.add(
FlippableImageObject(
id++, x, y,
grid = cardsGrid, gx = gridX, gy = gridY, altX = 2, altY = 4, isFaceUp = isFaceUp
)
)
}
}
if (includeJokers) {
result.add(
FlippableImageObject(
id++, x, y,
grid = cardsGrid, gx = 0, gy = 4, altX = 2, altY = 4, isFaceUp = isFaceUp
)
)
result.add(
FlippableImageObject(
id++, x, y,
grid = cardsGrid, gx = 1, gy = 4, altX = 2, altY = 4, isFaceUp = isFaceUp
)
)
}
}
return result
}
private fun jokers(startId: Int, x: Int = deckPoint.x, y: Int = deckPoint.y, isFaceUp: Boolean = false) = listOf(
FlippableImageObject(
startId, x, y, grid = cardsGrid, gx = 0, gy = 4, altX = 2, altY = 4, isFaceUp = isFaceUp
),
FlippableImageObject(
startId + 1, x, y, grid = cardsGrid, gx = 1, gy = 4, altX = 2, altY = 4, isFaceUp = isFaceUp
)
)
private val deckPoint = SpecialPoint("deck", x = 78, y = 265, isFaceUp = false, changeZOrder = ChangeZOrder.BOTTOM, snapDistance = 12)
private val handArea = SpecialArea("hand", AreaType.PRIVATE, 0, playingAreaHeight - 90, playingAreaWidth, 90)
private val tableArea = SpecialArea("table", AreaType.PUBLIC, 0, 75, playingAreaWidth, 441 - 75)
private val cardRules = """
_Cards_
Drag cards from the table to your `hand` (the area below the table).
Only you will see these cards, to everyone else, the cards will `vanish`.
However, everybody can see how many cards you are holding from the number in brackets
next to your name.
When you drag cards from your `hand` into the center of the table, they will
appear `face up`, for all to see. While dragging the card will have a red tint,
as a warning that it will become visible to others.
_Turning Cards Over_
To turn a card over, double click it.
_Dealing_
Use the `deal` command, to deal cards to all players. e.g. _:deal 7_ will
deal seven cards from the main deck to each player.
Use the `take` command, to take cards from the deck. e.g. _:take 2_ will
add two extra cards to your hand from the main deck (to you only).
_Passing Cards_
Some card games, such as `Hearts`, you need to pass cards from your `hand` to another player.
Drag the card(s) from your `hand` to your opponent's name (at the edge of the table).
The cards will become visible to other players, but will be `face down`.
Alternatively, you can turn the card `face down` by double clicking it, and then
drag it from your `hand` onto the table.
""".trimIndent()
val normalCardsVariation = GameVariation(
"normal",
"Cards",
minPlayers = 2,
maxPlayers = 6,
grids = listOf(cardsGrid),
backgroundObjects = listOf(
ImageObject(-1, 400, 300, path = "backgrounds/khaki.jpg", draggable = false),
ImageObject(-2, 400, 276, path = "cardTable.png", draggable = false),
GridImageObject(-3, deckPoint.x, deckPoint.y, grid = cardsGrid, gx = 12, gy = 4)
),
playingObjects = packOfCards(),
foregroundObjects = listOf(
PieceAtPointCounter(0, deckPoint.x, deckPoint.y + 13, deckPoint.name)
),
specialAreas = listOf(handArea, tableArea),
specialPoints = listOf(deckPoint),
commands = listOf(
ShuffleCommand(deckPoint),
DealCommand(deckPoint, handArea, 50),
TakeCommand(deckPoint, handArea, 50),
RemoveCardCommand()
),
commandPrototypes = listOf(
CommandPrototype("Deal", "deal"),
CommandPrototype("Take", "take"),
CommandPrototype("Shuffle", "shuffle", isComplete = true)
),
avatarPositions = TopAndBottomAvatarPositions(61, 488, 220),
options = listOf(includeJokers, extraPack, extraPackWithJokers),
scoreSheetHasBidColumn = true,
rules = cardRules
)
// SPITE AND MALICE
// The main deck of cards
private val samDeckPoint =
SpecialPoint("samDeck", x = 30, y = 550, isFaceUp = false, changeZOrder = ChangeZOrder.BOTTOM, snapDistance = 12)
// Each player's stacks. Initially empty, but the :stacks N command will fill them up.
private val samStackPoints = (0..3).map { n ->
SpecialPoint("stack$n", 100 + n * 200, 440)
}
private val samStackCounters = (0..3).map { n ->
PieceAtPointCounter(-100 - n, samStackPoints[n].x - 30, samStackPoints[n].y, "stack$n")
}
// The distance between the A..Q piles.
private const val samPileSpread = 120
// The point where A..Q are placed. Initially empty, except for the [samPileIndicators] on the background layer.
private val samPilePoints = (0..3).map { n ->
SpecialPoint("stack$n", 400 - 3 * samPileSpread / 2 + n * samPileSpread, 80, isFaceUp = true)
}
private val samReplaceButtons = (0..3).map { n ->
CommandButton(
"Replace",
CommandPrototype("Replace", "replace", listOf(n.toString()), isComplete = true),
x = samPilePoints[n].x,
y = samPilePoints[n].y - 60
)
}
// On the background layer. Indicates where A..Q are to be placed.
private val samPileIndicators = (0..3).map { n ->
GridImageObject(-n, samPilePoints[n].x, samPilePoints[n].y, draggable = false, grid = cardsGrid, gx = 12, gy = 4)
}
// The PRIVATE area, (shared by all players) for their hands.
private val samHandArea =
SpecialArea("samHand", AreaType.PRIVATE, 100, playingAreaHeight - 90, playingAreaWidth - 100, 90)
// The area of for "discard" cards, arranged in columns (with the aid of a Rectangular Grid).
private val samTableArea =
SpecialArea(
"samTableArea", AreaType.PUBLIC, 0, 150, playingAreaWidth, playingAreaHeight - 90 - 150 - 90,
snap = RectangularSnapToGrid(800 / 4 / 4, 30)
)
// The PUBLIC area at the top, where A..Q are pile up.
// No grid, because the cards are placed on the points [samPilePoints].
private val samPilesArea =
SpecialArea("samTable", AreaType.PUBLIC, 0, 0, playingAreaWidth, 150)
val spiteAndMalice = GameVariation(
"spiteAndMalice", "Spite and Malice",
minPlayers = 4, maxPlayers = 4,
grids = normalCardsVariation.grids,
backgroundObjects = listOf(
ImageObject(-1, 400, 300, path = "backgrounds/spiteAndMalice.jpg", draggable = false)
) + samPileIndicators,
playingObjects = packOfCards(packs = 5, x = samDeckPoint.x, y = samDeckPoint.y, includeJokers = true),
foregroundObjects = samStackCounters + listOf(
PieceAtPointCounter(-200, samDeckPoint.x, samDeckPoint.y + 13, samDeckPoint.name)
),
specialPoints = listOf(samDeckPoint) + samPilePoints + samStackPoints,
specialAreas = listOf(
samHandArea, samTableArea, samPilesArea
),
commands = listOf(
ShuffleCommand(samDeckPoint),
DealCommand(samDeckPoint, samHandArea, 50),
TakeCommand(samDeckPoint, samHandArea, 50),
StacksCommand(samDeckPoint, samStackPoints),
ReplaceCommand(samDeckPoint, samPilePoints)
),
commandPrototypes = listOf(
CommandPrototype("Deal", "deal"),
CommandPrototype("Take", "take"),
CommandPrototype("Shuffle", "shuffle", isComplete = true),
CommandPrototype("Stacks", "stacks"),
),
avatarPositions = HorizontalAvatarPositions(503),
options = emptyList(),
buttons = samReplaceButtons,
rules = """
_Spite and Malice_
Thanks Katy, for introducing this game to our family `:-)`
[Spite and Malice rules on Wikipedia|https://en.wikipedia.org/wiki/Spite_and_Malice]
""".trimIndent() + "\n$cardRules"
)
class GrabTheAcesShuffleCommand(
private val deck: SpecialPoint
) : Command(CommandInfo("shuffle", "Spread the cards"), isInitialiser = true) {
override suspend fun run(from: ConnectedPlayer, parameters: List<String>): String? {
val game = from.game
shuffleCardsAtPoint(game, deck)
for (card in game.playingObjects().filter { deck.contains(it.x, it.y) }) {
card.x = Random.nextInt(400) + 200
card.y = Random.nextInt(300) + 120
if (card is FlippableImageObject) {
card.isFaceUp = true
println("Grab shuffle")
game.send(FaceUpOrDown(card.id, card.isFaceUp))
}
game.send(MoveObject(card.id, card.x, card.y))
}
return null
}
}
val grabTheAces = GameVariation(
name = "grabTheAces",
label = "Grab the Aces",
minPlayers = normalCardsVariation.minPlayers,
maxPlayers = normalCardsVariation.maxPlayers,
grids = normalCardsVariation.grids,
backgroundObjects = normalCardsVariation.backgroundObjects,
playingObjects = normalCardsVariation.playingObjects,
foregroundObjects = normalCardsVariation.foregroundObjects,
specialAreas = normalCardsVariation.specialAreas,
specialPoints = normalCardsVariation.specialPoints,
commands = normalCardsVariation.commands.filter { it.name == "shuffle" } + listOf(
GrabTheAcesShuffleCommand(deckPoint)
),
commandPrototypes = normalCardsVariation.commandPrototypes,
avatarPositions = normalCardsVariation.avatarPositions,
options = emptyList(),
buttons = emptyList(),
rules = """
_Grab The Aces_
A frantic free-for-all, the aim of the game is to grab as many aces as possible,
and place them in your hand (the area below the table).
Shout out, when you grab one. The winner is the person with the most aces.
_Why?_
While this `may` be fun, the reason I created this game was to test my program.
If two players grab a single card at the same time, one player will get there first,
and the other player will lose out, and their `drag` will be cancelled.
It's very difficult to test this with only one mouse ;-) so I need an opponent.
As everyone is looking for Aces, as soon as one appears, everyone will try to grab it!
""".trimIndent() + "\n$cardRules"
)
val cards = GameType(
"cards", "Cards", "cardGames.png",
listOf(normalCardsVariation, spiteAndMalice, grabTheAces)
)