First working version
This commit is contained in:
@ -1,26 +0,0 @@
|
||||
import Foundation
|
||||
import WebSocketKit
|
||||
|
||||
class AbstractPlayer {
|
||||
|
||||
let name: PlayerName
|
||||
|
||||
var socket: WebSocket?
|
||||
|
||||
init(name: PlayerName, socket: WebSocket? = nil) {
|
||||
self.name = name
|
||||
self.socket = socket
|
||||
}
|
||||
|
||||
init(player: Player) {
|
||||
self.name = player.name
|
||||
self.socket = player.socket
|
||||
}
|
||||
}
|
||||
|
||||
extension AbstractPlayer: Equatable {
|
||||
|
||||
static func == (lhs: AbstractPlayer, rhs: AbstractPlayer) -> Bool {
|
||||
lhs.name == rhs.name
|
||||
}
|
||||
}
|
@ -1,51 +1,61 @@
|
||||
import Foundation
|
||||
import WebSocketKit
|
||||
|
||||
final class BiddingPlayer {
|
||||
|
||||
let name: String
|
||||
|
||||
var socket: WebSocket?
|
||||
|
||||
let cards: [PlayableCard]
|
||||
final class BiddingPlayer: Player {
|
||||
|
||||
var isStillBidding = true
|
||||
|
||||
var isAllowedToOfferWedding = true
|
||||
var isAllowedToOfferWedding: Bool
|
||||
|
||||
var offersWedding = false
|
||||
var selectsGame = false
|
||||
|
||||
var wouldAcceptWedding = false
|
||||
|
||||
init(player: DealingPlayer, cards: [PlayableCard]) {
|
||||
self.name = player.name
|
||||
self.socket = player.socket
|
||||
self.cards = cards
|
||||
}
|
||||
}
|
||||
|
||||
extension BiddingPlayer: Player {
|
||||
|
||||
var canOfferWedding: Bool {
|
||||
rawCards.canOfferWedding
|
||||
init(player: DealingPlayer) {
|
||||
isAllowedToOfferWedding = true
|
||||
super.init(player: player)
|
||||
}
|
||||
|
||||
var rawCards: [Card] {
|
||||
cards.map { $0.card }
|
||||
init(player: WeddingPlayer) {
|
||||
isStillBidding = !player.offersWedding
|
||||
isAllowedToOfferWedding = false
|
||||
super.init(player: player)
|
||||
}
|
||||
|
||||
func canPlay(game: GameType) -> Bool {
|
||||
guard let suit = game.calledSuit else {
|
||||
if game == .hochzeit {
|
||||
return canOfferWedding
|
||||
}
|
||||
return true
|
||||
}
|
||||
let sorter = game.sortingType
|
||||
guard sorter.hasCardToCall(suit, in: cards) else {
|
||||
// Player needs at least one card of the called suit
|
||||
return false
|
||||
}
|
||||
let ace = Card(suit, .ass)
|
||||
return !cards.contains(ace)
|
||||
}
|
||||
|
||||
var actions: [PlayerAction] {
|
||||
override var actions: [PlayerAction] {
|
||||
guard isStillBidding else {
|
||||
return []
|
||||
}
|
||||
guard canOfferWedding, isAllowedToOfferWedding, !offersWedding else {
|
||||
return [.increaseOrMatchGame, .withdrawFromAuction]
|
||||
var actions: [PlayerAction] = isNextActor ? [.increaseOrMatchGame, .withdrawFromAuction] : []
|
||||
if canOfferWedding {
|
||||
actions.append(.offerWedding)
|
||||
}
|
||||
return [.increaseOrMatchGame, .withdrawFromAuction, .offerWedding]
|
||||
return actions
|
||||
}
|
||||
|
||||
var playedCard: Card? {
|
||||
nil
|
||||
override var points: Int? {
|
||||
get { nil }
|
||||
set { }
|
||||
}
|
||||
}
|
||||
|
||||
extension BiddingPlayer {
|
||||
|
||||
var canOfferWedding: Bool {
|
||||
cards.canOfferWedding
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +1,36 @@
|
||||
import Foundation
|
||||
import WebSocketKit
|
||||
|
||||
final class DealingPlayer: AbstractPlayer {
|
||||
|
||||
var cards: [PlayableCard] = []
|
||||
final class DealingPlayer: Player {
|
||||
|
||||
var didDouble: Bool? = nil
|
||||
|
||||
override var isNextActor: Bool {
|
||||
get { didDouble == nil }
|
||||
set { }
|
||||
}
|
||||
|
||||
override var actions: [PlayerAction] {
|
||||
didDouble == nil ? [.initialDoubleCost, .noDoubleCost] : []
|
||||
}
|
||||
|
||||
init(player: WaitingPlayer) {
|
||||
super.init(player: player)
|
||||
}
|
||||
}
|
||||
|
||||
extension DealingPlayer: Player {
|
||||
|
||||
var actions: [PlayerAction] {
|
||||
didDouble == nil ? [.initialDoubleCost, .noDoubleCost] : []
|
||||
override var numberOfDoubles: Int {
|
||||
get { didDouble == true ? 1 : 0 }
|
||||
set { }
|
||||
}
|
||||
|
||||
var playedCard: Card? {
|
||||
nil
|
||||
override var leadsGame: Bool {
|
||||
get { false }
|
||||
set { }
|
||||
}
|
||||
|
||||
override var points: Int? {
|
||||
get { nil }
|
||||
set { }
|
||||
}
|
||||
|
||||
}
|
||||
|
21
Sources/App/Model/Players/FinishedPlayer.swift
Normal file
21
Sources/App/Model/Players/FinishedPlayer.swift
Normal file
@ -0,0 +1,21 @@
|
||||
import Foundation
|
||||
|
||||
final class FinishedPlayer: Player {
|
||||
|
||||
let tricks: [Trick]
|
||||
|
||||
init(player: PlayingPlayer) {
|
||||
self.tricks = player.wonTricks
|
||||
super.init(player: player)
|
||||
}
|
||||
|
||||
override var points: Int? {
|
||||
get { tricks.map { $0.points }.reduce(0, +) }
|
||||
set { }
|
||||
}
|
||||
|
||||
override var actions: [PlayerAction] {
|
||||
[.deal]
|
||||
}
|
||||
|
||||
}
|
@ -1,18 +1,57 @@
|
||||
import Foundation
|
||||
import WebSocketKit
|
||||
|
||||
protocol Player: AnyObject {
|
||||
class Player {
|
||||
|
||||
var name: String { get }
|
||||
let name: PlayerName
|
||||
|
||||
var socket: WebSocket? { get set }
|
||||
var socket: WebSocket?
|
||||
|
||||
var playedCard: Card? { get }
|
||||
var playedCard: Card?
|
||||
|
||||
var actions: [PlayerAction] { get }
|
||||
var isNextActor: Bool
|
||||
|
||||
var cards: [PlayableCard] { get }
|
||||
var cards: [Card]
|
||||
|
||||
var numberOfDoubles: Int
|
||||
|
||||
var leadsGame: Bool
|
||||
|
||||
var points: Int?
|
||||
|
||||
init(name: PlayerName, socket: WebSocket? = nil) {
|
||||
self.name = name
|
||||
self.socket = socket
|
||||
self.cards = []
|
||||
self.isNextActor = false
|
||||
self.playedCard = nil
|
||||
self.numberOfDoubles = 0
|
||||
self.leadsGame = false
|
||||
self.points = nil
|
||||
}
|
||||
|
||||
init(player: Player) {
|
||||
self.name = player.name
|
||||
self.socket = player.socket
|
||||
self.cards = player.cards
|
||||
self.isNextActor = false
|
||||
self.playedCard = player.playedCard
|
||||
self.numberOfDoubles = player.numberOfDoubles
|
||||
self.leadsGame = player.leadsGame
|
||||
self.points = player.points
|
||||
}
|
||||
|
||||
var actions: [PlayerAction] {
|
||||
[]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Player: Equatable {
|
||||
|
||||
static func == (lhs: Player, rhs: Player) -> Bool {
|
||||
lhs.name == rhs.name
|
||||
}
|
||||
}
|
||||
|
||||
extension Player {
|
||||
@ -36,6 +75,7 @@ extension Player {
|
||||
self.socket = socket
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func disconnect() -> Bool {
|
||||
guard let socket = socket else {
|
||||
return false
|
||||
@ -50,8 +90,18 @@ extension Player {
|
||||
}
|
||||
|
||||
|
||||
func send(_ info: TableInfo) {
|
||||
try? socket?.send(encodeJSON(info))
|
||||
@discardableResult
|
||||
func send(_ info: TableInfo) -> Bool {
|
||||
guard let socket = socket else {
|
||||
return false
|
||||
}
|
||||
do {
|
||||
try socket.send(encodeJSON(info))
|
||||
} catch {
|
||||
print("Failed to send info: \(error)")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// MARK: Actions
|
||||
@ -61,3 +111,10 @@ extension Player {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Player: CustomStringConvertible {
|
||||
|
||||
var description: String {
|
||||
name
|
||||
}
|
||||
}
|
||||
|
@ -1,30 +1,139 @@
|
||||
import Foundation
|
||||
import WebSocketKit
|
||||
|
||||
final class PlayingPlayer: AbstractPlayer {
|
||||
/**
|
||||
Specifies the number of cards of the called suit that a player must have
|
||||
to be allowed to play any card of the suit instead of having to play the ace.
|
||||
*/
|
||||
private let numberOfCardsToProtectAce = 4
|
||||
|
||||
var playedCard: Card? = nil
|
||||
|
||||
var cards: [PlayableCard]
|
||||
|
||||
var leadsGame = false
|
||||
final class PlayingPlayer: Player {
|
||||
|
||||
var canStillRaise = true
|
||||
|
||||
init(player: BiddingPlayer) {
|
||||
self.cards = player.cards
|
||||
var isCalledWithAce: Card?
|
||||
|
||||
/// All tricks won by the player in this game
|
||||
var wonTricks: [Trick] = []
|
||||
|
||||
init(player: Player, leads: Bool, calledAce ace: Card?) {
|
||||
super.init(player: player)
|
||||
leadsGame = leads
|
||||
if let ace = ace, cards.contains(ace) {
|
||||
isCalledWithAce = ace
|
||||
} else {
|
||||
isCalledWithAce = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension PlayingPlayer: Player {
|
||||
|
||||
|
||||
var actions: [PlayerAction] {
|
||||
guard canStillRaise, !leadsGame else {
|
||||
override var actions: [PlayerAction] {
|
||||
guard canStillRaise, leadsGame == (isCalledWithAce != nil) else {
|
||||
return []
|
||||
}
|
||||
return [.doubleDuringGame]
|
||||
}
|
||||
|
||||
func play(card: Card) {
|
||||
playedCard = card
|
||||
cards = cards.filter { $0 != card }
|
||||
if card == isCalledWithAce {
|
||||
leadsGame.toggle()
|
||||
isCalledWithAce = nil
|
||||
}
|
||||
}
|
||||
|
||||
func switchLead() {
|
||||
leadsGame.toggle()
|
||||
}
|
||||
|
||||
func sortCards(for game: GameType) {
|
||||
cards = cards.sortedCards(forGame: game)
|
||||
}
|
||||
|
||||
func canPlay(card: Card, for trick: Trick, in game: GameType) -> Bool {
|
||||
playableCards(for: trick, in: game).contains { $0.card == card && $0.isPlayable }
|
||||
}
|
||||
|
||||
func playableCards(for trick: Trick, in game: GameType) -> [PlayableCard] {
|
||||
guard isNextActor else {
|
||||
return cards.unplayable
|
||||
}
|
||||
guard cards.count > 1 else {
|
||||
// Last card can always be played
|
||||
return cards.playable
|
||||
}
|
||||
guard let firstCard = trick.first else {
|
||||
return playableCardsForStarter(game: game)
|
||||
}
|
||||
|
||||
let sorter = game.sortingType
|
||||
|
||||
guard sorter.isTrump(firstCard) else {
|
||||
return playableCardsFollowing(suit: firstCard.suit, game: game)
|
||||
}
|
||||
guard !sorter.hasTrump(in: cards) else {
|
||||
// Must follow with trump
|
||||
return cards.map { $0.playable(sorter.isTrump($0)) }
|
||||
}
|
||||
// Can play any card if not in calling game
|
||||
guard let suit = game.calledSuit else {
|
||||
return cards.playable
|
||||
}
|
||||
// Can play any card, except the called ace
|
||||
let ace = Card(suit, .ass)
|
||||
return cards.map { $0.playable($0 != ace) }
|
||||
}
|
||||
|
||||
private func playableCardsFollowing(suit playedSuit: Card.Suit, game: GameType) -> [PlayableCard] {
|
||||
let sorter = game.sortingType
|
||||
let suitCards = sorter.cards(with: playedSuit, in: cards)
|
||||
|
||||
func followSuit() -> [PlayableCard] {
|
||||
cards.map { $0.playable(!sorter.isTrump($0) && $0.suit == playedSuit) }
|
||||
}
|
||||
|
||||
guard let calledSuit = game.calledSuit else {
|
||||
return suitCards.isEmpty ? cards.playable : followSuit()
|
||||
}
|
||||
let ace = Card(calledSuit, .ass)
|
||||
guard !suitCards.isEmpty else {
|
||||
// Exclude called ace, all others allowed
|
||||
return cards.map { $0.playable($0 != ace) }
|
||||
}
|
||||
guard calledSuit == playedSuit else {
|
||||
// Must follow suit (called ace not present)
|
||||
return followSuit()
|
||||
}
|
||||
|
||||
// The called suit is played, must commit ace
|
||||
guard cards.contains(ace) else {
|
||||
// Must follow suit
|
||||
return followSuit()
|
||||
}
|
||||
// Must play ace
|
||||
return cards.map { $0.playable($0 == ace) }
|
||||
}
|
||||
|
||||
private func playableCardsForStarter(game: GameType) -> [PlayableCard] {
|
||||
guard let suit = game.calledSuit else {
|
||||
return cards.playable
|
||||
}
|
||||
let ace = Card(suit, .ass)
|
||||
// Check if called ace exists, to prohibit other cards of the same suit
|
||||
guard cards.contains(ace) else {
|
||||
return cards.playable
|
||||
}
|
||||
// Jodeln
|
||||
if cards.count == numberOfCardsPerPlayer,
|
||||
cards.suitCount(suit, in: game) >= numberOfCardsToProtectAce {
|
||||
return cards.playable
|
||||
}
|
||||
|
||||
// Only ace allowed for the called suit
|
||||
return cards.map { $0.playable($0.suit != suit || $0.symbol.isTrumpOrAce) }
|
||||
}
|
||||
|
||||
var currentPoints: Int {
|
||||
wonTricks.map { $0.points }.reduce(0, +)
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,22 @@
|
||||
import Foundation
|
||||
import WebSocketKit
|
||||
|
||||
final class WaitingPlayer: AbstractPlayer {
|
||||
final class WaitingPlayer: Player {
|
||||
|
||||
var canStartGame: Bool = false
|
||||
}
|
||||
|
||||
extension WaitingPlayer: Player {
|
||||
|
||||
var actions: [PlayerAction] {
|
||||
override var actions: [PlayerAction] {
|
||||
canStartGame ? [.deal] : []
|
||||
}
|
||||
|
||||
var cards: [PlayableCard] {
|
||||
[]
|
||||
override var leadsGame: Bool {
|
||||
get { false }
|
||||
set { }
|
||||
}
|
||||
|
||||
var playedCard: Card? {
|
||||
nil
|
||||
override var points: Int? {
|
||||
get { nil }
|
||||
set { }
|
||||
}
|
||||
|
||||
}
|
||||
|
89
Sources/App/Model/Players/WeddingPlayer.swift
Normal file
89
Sources/App/Model/Players/WeddingPlayer.swift
Normal file
@ -0,0 +1,89 @@
|
||||
import Foundation
|
||||
|
||||
final class WeddingPlayer: Player {
|
||||
|
||||
enum State {
|
||||
case requiresAction
|
||||
case offersWedding
|
||||
case wouldAcceptWedding
|
||||
case withdrawnFromAuction
|
||||
case selectsGame
|
||||
}
|
||||
|
||||
var state: State
|
||||
|
||||
var requiresAction: Bool {
|
||||
state == .requiresAction
|
||||
}
|
||||
|
||||
var selectsGame: Bool {
|
||||
get {
|
||||
state == .selectsGame
|
||||
}
|
||||
set {
|
||||
state = .selectsGame
|
||||
}
|
||||
}
|
||||
|
||||
var wouldAcceptWedding: Bool {
|
||||
state == .wouldAcceptWedding
|
||||
}
|
||||
|
||||
var offersWedding: Bool {
|
||||
state == .offersWedding
|
||||
}
|
||||
|
||||
init(player: BiddingPlayer, offersWedding: Bool) {
|
||||
self.state = offersWedding ? .offersWedding : .requiresAction
|
||||
super.init(player: player)
|
||||
}
|
||||
|
||||
override var actions: [PlayerAction] {
|
||||
guard state == .requiresAction else {
|
||||
return []
|
||||
}
|
||||
return [.increaseOrMatchGame, .withdrawFromAuction, .acceptWedding]
|
||||
}
|
||||
|
||||
override var isNextActor: Bool {
|
||||
get {
|
||||
switch state {
|
||||
case .requiresAction, .selectsGame:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
set { }
|
||||
}
|
||||
|
||||
override var points: Int? {
|
||||
get { nil }
|
||||
set { }
|
||||
}
|
||||
|
||||
override var leadsGame: Bool {
|
||||
get { offersWedding || selectsGame }
|
||||
set { }
|
||||
}
|
||||
|
||||
func canExchange(card: Card) -> Bool {
|
||||
cards.filter { !$0.isTrump(in: .hochzeit) }.contains(card)
|
||||
}
|
||||
|
||||
var exchangeableCards: [PlayableCard] {
|
||||
cards.map { $0.playable(!$0.isTrump(in: .hochzeit)) }
|
||||
}
|
||||
|
||||
func replaceWeddingCard(with card: Card) -> Card {
|
||||
let ownCardIndex = cards.firstIndex { $0.isTrump(in: .hochzeit)}!
|
||||
let ownCard = cards.remove(at: ownCardIndex)
|
||||
cards.append(card)
|
||||
cards = cards.sortedCards(forGame: .hochzeit)
|
||||
return ownCard
|
||||
}
|
||||
|
||||
func replace(_ card: Card, with trumpCard: Card) {
|
||||
cards = (cards.filter { $0 != card } + [trumpCard]).sortedCards(forGame: .hochzeit)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user