First working version

This commit is contained in:
Christoph Hagen
2021-12-18 15:08:43 +01:00
parent c9853dee28
commit 49787db1aa
32 changed files with 1416 additions and 415 deletions

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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 { }
}
}

View 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]
}
}

View File

@ -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
}
}

View File

@ -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, +)
}
}

View File

@ -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 { }
}
}

View 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)
}
}