diff --git a/examples/swift/Tennis.playground/Contents.swift b/examples/swift/Tennis.playground/Contents.swift new file mode 100644 index 0000000..fe1ba4b --- /dev/null +++ b/examples/swift/Tennis.playground/Contents.swift @@ -0,0 +1,358 @@ +import UIKit + +enum GameState { + case warmup + case playing + case deuce + case won + case lost +} + +enum Points { + case love + case fifteen + case thirty + case forty + case avantage + case won +} + +struct Player { + let points : Points + + init(points: Points = .love) { + self.points = points + } +} + +struct Game { + + let state : GameState + + let players : Players + + init(state: GameState = .warmup, players: Players = Players()) { + self.state = state + self.players = players + } + +} + +enum PlayerType { + case server + case reciever +} + +struct Players { + + let server: Player + let reciever: Player + + init(server: Player = Player(), reciever: Player = Player()) { + self.server = server + self.reciever = reciever + } + +} + +struct Umpire { + + static func awardPoints(to player: PlayerType, game: Game) -> Game { + + // Handle special case: if opponent has advantage, reset to deuce + if game.state == .playing { + if player == .server && game.players.reciever.points == .avantage { + let resetPlayers = Players( + server: Player(points: .forty), + reciever: Player(points: .forty) + ) + return Game(state: .deuce, players: resetPlayers) + } else if player == .reciever && game.players.server.points == .avantage { + let resetPlayers = Players( + server: Player(points: .forty), + reciever: Player(points: .forty) + ) + return Game(state: .deuce, players: resetPlayers) + } + } + + // First, increment the player's points + let updatedPlayers = incrementPlayerPoints(player: player, players: game.players) + + // Check if game is won after awarding points + let isGameWon = byAwardingPlayerPointIsGameWon(to: player, game: Game(state: game.state, players: updatedPlayers)) + + if(isGameWon){ + + print("Game won by (\(player))") + return Game(state: .won, players: updatedPlayers) + } + + // Check if game should be in deuce state + let newState = determineGameState(players: updatedPlayers, currentState: game.state) + + return Game(state: newState, players: updatedPlayers) + + } + + static func incrementPlayerPoints(player: PlayerType, players: Players) -> Players { + if player == .server { + let newServerPoints = nextPoint(from: players.server.points) + let updatedServer = Player(points: newServerPoints) + return Players(server: updatedServer, reciever: players.reciever) + } else { + let newReceiverPoints = nextPoint(from: players.reciever.points) + let updatedReceiver = Player(points: newReceiverPoints) + return Players(server: players.server, reciever: updatedReceiver) + } + } + + static func nextPoint(from currentPoints: Points) -> Points { + switch currentPoints { + case .love: + return .fifteen + case .fifteen: + return .thirty + case .thirty: + return .forty + case .forty: + return .avantage + case .avantage: + return .won + case .won: + return .won + } + } + + static func determineGameState(players: Players, currentState: GameState) -> GameState { + let serverPoints = players.server.points + let receiverPoints = players.reciever.points + + // Check for deuce condition (both at forty) + if serverPoints == .forty && receiverPoints == .forty { + return .deuce + } + + // If either player has advantage, the game is in playing state + if serverPoints == .avantage || receiverPoints == .avantage { + return .playing + } + + return .playing + } + + static func byAwardingPlayerPointIsGameWon(to player: PlayerType, game: Game) -> Bool { + + let serverPoints = game.players.server.points + let receiverPoints = game.players.reciever.points + + if player == .server { + // Server wins if: + // 1. They have advantage and receiver doesn't have forty (advantage -> win) + // 2. They have forty and receiver has less than forty (straight win) + // 3. They would get .won points (meaning they had advantage and got another point) + return (serverPoints == .avantage && receiverPoints != .forty) || + (serverPoints == .forty && receiverPoints != .forty && receiverPoints != .avantage) || + (serverPoints == .won) + } else { + // Receiver wins if: + // 1. They have advantage and server doesn't have forty (advantage -> win) + // 2. They have forty and server has less than forty (straight win) + // 3. They would get .won points (meaning they had advantage and got another point) + return (receiverPoints == .avantage && serverPoints != .forty) || + (receiverPoints == .forty && serverPoints != .forty && serverPoints != .avantage) || + (receiverPoints == .won) + } + + } + +} + + +struct Scoreboard { + + static func printGame(game: Game) { + + print("Game state: \(game.state)") + print("Player 1: \(game.players.server.points). Player 2: \(game.players.reciever.points).") + + } +} + + +// MARK: - Playground Tests +func testInitialGameState() { + print("\n๐Ÿงช Testing Initial Game State...") + let game = Game() + + assertEqual(game.state, .warmup, "Game should start in warmup state") + assertEqual(game.players.server.points, .love, "Server should start with love") + assertEqual(game.players.reciever.points, .love, "Receiver should start with love") +} + +func testPointProgression() { + print("\n๐Ÿงช Testing Point Progression...") + var game = Game() + + // Award first point to server + game = Umpire.awardPoints(to: .server, game: game) + assertEqual(game.players.server.points, .fifteen, "Server should have fifteen after first point") + assertEqual(game.players.reciever.points, .love, "Receiver should still have love") + assertEqual(game.state, .playing, "Game should be in playing state") + + // Award second point to server + game = Umpire.awardPoints(to: .server, game: game) + assertEqual(game.players.server.points, .thirty, "Server should have thirty after second point") + + // Award third point to server + game = Umpire.awardPoints(to: .server, game: game) + assertEqual(game.players.server.points, .forty, "Server should have forty after third point") +} + +func testServerWinsGame() { + print("\n๐Ÿงช Testing Server Wins Game...") + var game = Game() + + // Server wins 4-0 + game = Umpire.awardPoints(to: .server, game: game) // 15-0 + game = Umpire.awardPoints(to: .server, game: game) // 30-0 + game = Umpire.awardPoints(to: .server, game: game) // 40-0 + game = Umpire.awardPoints(to: .server, game: game) // Game won + + assertEqual(game.state, .won, "Game should be won") +} + +func testDeuceScenario() { + print("\n๐Ÿงช Testing Deuce Scenario...") + var game = Game() + + // Both players reach 40 (deuce) + game = Umpire.awardPoints(to: .server, game: game) // 15-0 + game = Umpire.awardPoints(to: .server, game: game) // 30-0 + game = Umpire.awardPoints(to: .server, game: game) // 40-0 + + game = Umpire.awardPoints(to: .reciever, game: game) // 40-15 + game = Umpire.awardPoints(to: .reciever, game: game) // 40-30 + game = Umpire.awardPoints(to: .reciever, game: game) // 40-40 (deuce) + + assertEqual(game.state, .deuce, "Game should be in deuce state") + assertEqual(game.players.server.points, .forty, "Server should have forty") + assertEqual(game.players.reciever.points, .forty, "Receiver should have forty") +} + +func testAdvantageAndWin() { + print("\n๐Ÿงช Testing Advantage and Win...") + var game = Game() + + // Set up deuce scenario + game = Game(state: .deuce, players: Players( + server: Player(points: .forty), + reciever: Player(points: .forty) + )) + + // Server gets advantage + game = Umpire.awardPoints(to: .server, game: game) + assertEqual(game.players.server.points, .avantage, "Server should have advantage") + assertEqual(game.players.reciever.points, .forty, "Receiver should still have forty") + assertEqual(game.state, .playing, "Game should be in playing state") + + // Server wins the game + game = Umpire.awardPoints(to: .server, game: game) + assertEqual(game.state, .won, "Game should be won") +} + +func testCompleteDeuceBattle() { + print("\n๐Ÿงช Testing Complete Deuce Battle...") + var game = Game() + + // Set up deuce + game = Game(state: .deuce, players: Players( + server: Player(points: .forty), + reciever: Player(points: .forty) + )) + + // Server gets advantage + game = Umpire.awardPoints(to: .server, game: game) + assertEqual(game.state, .playing, "Game should be playing after advantage") + assertEqual(game.players.server.points, .avantage, "Server should have advantage") + + // Receiver equalizes - back to deuce + game = Umpire.awardPoints(to: .reciever, game: game) + assertEqual(game.state, .deuce, "Game should be back to deuce") + assertEqual(game.players.server.points, .forty, "Server should be back to forty") + assertEqual(game.players.reciever.points, .forty, "Receiver should be back to forty") + + // Receiver gets advantage this time + game = Umpire.awardPoints(to: .reciever, game: game) + assertEqual(game.state, .playing, "Game should be playing with receiver advantage") + assertEqual(game.players.reciever.points, .avantage, "Receiver should have advantage") + + // Receiver wins the game + game = Umpire.awardPoints(to: .reciever, game: game) + assertEqual(game.state, .won, "Game should be won by receiver") +} + +func testAdvantageBackToDeuce() { + print("\n๐Ÿงช Testing Advantage Back to Deuce...") + var game = Game() + + // Set up advantage scenario - server has advantage + game = Game(state: .playing, players: Players( + server: Player(points: .avantage), + reciever: Player(points: .forty) + )) + + // Receiver wins point, should go back to deuce + game = Umpire.awardPoints(to: .reciever, game: game) + assertEqual(game.state, .deuce, "Game should be back to deuce") + assertEqual(game.players.server.points, .forty, "Server should be back to forty") + assertEqual(game.players.reciever.points, .forty, "Receiver should be back to forty") +} + +func testReceiverWinsGame() { + print("\n๐Ÿงช Testing Receiver Wins Game...") + var game = Game() + + // Receiver wins 4-1 + game = Umpire.awardPoints(to: .server, game: game) // 15-0 + game = Umpire.awardPoints(to: .reciever, game: game) // 15-15 + game = Umpire.awardPoints(to: .reciever, game: game) // 15-30 + game = Umpire.awardPoints(to: .reciever, game: game) // 15-40 + game = Umpire.awardPoints(to: .reciever, game: game) // Game won by receiver + + assertEqual(game.state, .won, "Game should be won by receiver") +} + +// Run the tests +print("๐Ÿธ Running Tennis Game Tests...") + +testInitialGameState() +testPointProgression() +testServerWinsGame() +testDeuceScenario() +testAdvantageAndWin() +testCompleteDeuceBattle() +testAdvantageBackToDeuce() +testReceiverWinsGame() + +print("\n๐ŸŽพ All tests completed!") + + +// Simple test assertion functions for playground +func assertEqual(_ actual: T, _ expected: T, _ message: String = "") { + if actual == expected { + print("โœ… PASS: \(message)") + } else { + print("โŒ FAIL: \(message)") + print(" Expected: \(expected)") + print(" Actual: \(actual)") + } +} + +func assertTrue(_ condition: Bool, _ message: String = "") { + if condition { + print("โœ… PASS: \(message)") + } else { + print("โŒ FAIL: \(message)") + } +} diff --git a/examples/swift/Tennis.playground/contents.xcplayground b/examples/swift/Tennis.playground/contents.xcplayground new file mode 100644 index 0000000..7f43ce8 --- /dev/null +++ b/examples/swift/Tennis.playground/contents.xcplayground @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/examples/swift/Tennis.playground/playground.xcworkspace/contents.xcworkspacedata b/examples/swift/Tennis.playground/playground.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..ca3329e --- /dev/null +++ b/examples/swift/Tennis.playground/playground.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/swift/Tennis.playground/playground.xcworkspace/xcuserdata/henryallsuch.xcuserdatad/UserInterfaceState.xcuserstate b/examples/swift/Tennis.playground/playground.xcworkspace/xcuserdata/henryallsuch.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..095b015 Binary files /dev/null and b/examples/swift/Tennis.playground/playground.xcworkspace/xcuserdata/henryallsuch.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/examples/swift/Tennis2.playground/Contents.swift b/examples/swift/Tennis2.playground/Contents.swift new file mode 100644 index 0000000..cde8ff8 --- /dev/null +++ b/examples/swift/Tennis2.playground/Contents.swift @@ -0,0 +1,300 @@ +import UIKit + +// Tennis game states +enum GameState { + case playing + case deuce + case won +} + +// Tennis points: 0, 15, 30, 40, advantage +enum Points { + case love // 0 points + case fifteen // 15 points + case thirty // 30 points + case forty // 40 points + case advantage // advantage (after deuce) +} + +// A tennis player with their current points +struct Player { + let points: Points + + init(points: Points = .love) { + self.points = points + } +} + +// The tennis game +struct Game { + let state: GameState + let player1: Player + let player2: Player + + init(state: GameState = .playing, player1: Player = Player(), player2: Player = Player()) { + self.state = state + self.player1 = player1 + self.player2 = player2 + } +} + +// Simple tennis scoring +struct TennisScorer { + + // Award a point to player 1 or player 2 + static func awardPoint(to playerNumber: Int, game: Game) -> Game { + + // Give the point to the player and update their score + if playerNumber == 1 { + let newPoints = getNextPoints(from: game.player1.points) + let newPlayer1 = Player(points: newPoints) + let updatedGame = Game(state: game.state, player1: newPlayer1, player2: game.player2) + return checkGameResult(updatedGame, whoScored: 1) + } else { + let newPoints = getNextPoints(from: game.player2.points) + let newPlayer2 = Player(points: newPoints) + let updatedGame = Game(state: game.state, player1: game.player1, player2: newPlayer2) + return checkGameResult(updatedGame, whoScored: 2) + } + } + + // What happens when you score a point? + static func getNextPoints(from currentPoints: Points) -> Points { + switch currentPoints { + case .love: return .fifteen + case .fifteen: return .thirty + case .thirty: return .forty + case .forty: return .advantage + case .advantage: return .advantage // Stay at advantage until you win + } + } + + // Check if someone won or if it's deuce + static func checkGameResult(_ game: Game, whoScored: Int) -> Game { + let p1Points = game.player1.points + let p2Points = game.player2.points + + // RULE 1: Win from advantage + // If you have advantage and score again, you win! + if didPlayerWin(whoScored: whoScored, p1Points: p1Points, p2Points: p2Points) { + print("๐ŸŽพ Player \(whoScored) wins the game!") + return Game(state: .won, player1: game.player1, player2: game.player2) + } + + // RULE 2: Check for deuce + // When both players have 40, it's deuce + if p1Points == .forty && p2Points == .forty { + return Game(state: .deuce, player1: game.player1, player2: game.player2) + } + + // RULE 3: Back to deuce from advantage + // If one player has advantage but the other scores, back to deuce + if shouldGoBackToDeuce(currentState: game.state, whoScored: whoScored, p1Points: p1Points, p2Points: p2Points) { + return Game(state: .deuce, + player1: Player(points: .forty), + player2: Player(points: .forty)) + } + + // Continue playing + return Game(state: .playing, player1: game.player1, player2: game.player2) + } + + // Helper: Did someone win the game? + static func didPlayerWin(whoScored: Int, p1Points: Points, p2Points: Points) -> Bool { + if whoScored == 1 && p1Points == .advantage { + return true + } + if whoScored == 2 && p2Points == .advantage { + return true + } + return false + } + + // Helper: Should we go back to deuce? + static func shouldGoBackToDeuce(currentState: GameState, whoScored: Int, p1Points: Points, p2Points: Points) -> Bool { + // Only relevant if we're already at deuce + if currentState != .deuce { + return false + } + + // Player 1 scored, but Player 2 had advantage + if whoScored == 1 && p2Points == .advantage { + return true + } + + // Player 2 scored, but Player 1 had advantage + if whoScored == 2 && p1Points == .advantage { + return true + } + + return false + } +} + +// Show the score in a friendly way +struct ScoreDisplay { + static func show(_ game: Game) { + let p1Score = friendlyScore(game.player1.points) + let p2Score = friendlyScore(game.player2.points) + + print("Score: \(p1Score) - \(p2Score)") + if game.state == .deuce { + print("It's DEUCE! ๐Ÿค") + } else if game.state == .won { + print("Game Over! ๐Ÿ†") + } + } + + static func friendlyScore(_ points: Points) -> String { + switch points { + case .love: return "0" + case .fifteen: return "15" + case .thirty: return "30" + case .forty: return "40" + case .advantage: return "ADV" + } + } +} + + +// ๐Ÿงช SIMPLE TESTS - Easy to read and understand! + +func testBasicScoring() { + print("\n๐Ÿธ Testing Basic Tennis Scoring...") + var game = Game() + + // Player 1 scores: 0 -> 15 + game = TennisScorer.awardPoint(to: 1, game: game) + ScoreDisplay.show(game) + checkEqual(game.player1.points, .fifteen, "Player 1 should have 15") + + // Player 1 scores again: 15 -> 30 + game = TennisScorer.awardPoint(to: 1, game: game) + ScoreDisplay.show(game) + checkEqual(game.player1.points, .thirty, "Player 1 should have 30") + + // Player 1 scores again: 30 -> 40 + game = TennisScorer.awardPoint(to: 1, game: game) + ScoreDisplay.show(game) + checkEqual(game.player1.points, .forty, "Player 1 should have 40") +} + +func testSimpleWin() { + print("\n๐Ÿธ Testing Simple Win...") + var game = Game() + + // Player 1 gets 40, Player 2 has 0 + game = Game(player1: Player(points: .forty), player2: Player(points: .love)) + + // Player 1 scores the winning point + game = TennisScorer.awardPoint(to: 1, game: game) + ScoreDisplay.show(game) + checkEqual(game.state, .won, "Game should be won") +} + +func testDeuce() { + print("\n๐Ÿธ Testing Deuce...") + var game = Game() + + // Get both players to 30, then they both score to reach 40-40 (deuce) + game = Game(player1: Player(points: .thirty), player2: Player(points: .thirty)) + + // Player 1 scores: 30 -> 40 + game = TennisScorer.awardPoint(to: 1, game: game) + // Player 2 scores: 30 -> 40 (this should trigger deuce) + game = TennisScorer.awardPoint(to: 2, game: game) + ScoreDisplay.show(game) + checkEqual(game.state, .deuce, "Should be deuce when both have 40") +} + +func testAdvantage() { + print("\n๐Ÿธ Testing Advantage...") + var game = Game() + + // Start at deuce + game = Game(state: .deuce, player1: Player(points: .forty), player2: Player(points: .forty)) + + // Player 1 scores from deuce + game = TennisScorer.awardPoint(to: 1, game: game) + ScoreDisplay.show(game) + checkEqual(game.player1.points, .advantage, "Player 1 should have advantage") +} + +func testWinFromAdvantage() { + print("\n๐Ÿธ Testing Win from Advantage...") + var game = Game() + + // Player 1 has advantage, Player 2 has 40 + game = Game(player1: Player(points: .advantage), player2: Player(points: .forty)) + + // Player 1 scores the winning point + game = TennisScorer.awardPoint(to: 1, game: game) + ScoreDisplay.show(game) + checkEqual(game.state, .won, "Player 1 should win the game") +} + +// ๐Ÿƒโ€โ™‚๏ธ Run all the simple tests +print("TENNIS GAME TESTS") +print("===================") + +testBasicScoring() +testSimpleWin() +testDeuce() +testAdvantage() +testWinFromAdvantage() + +print("\n All tests done!") + +// ๐ŸŽพ Let's play a quick demo game! +print("\n\n๐ŸŽพ DEMO GAME - Watch a complete tennis game!") +print("===============================================") + +var demoGame = Game() +print("Starting game...") +ScoreDisplay.show(demoGame) + +// Player 1 scores some points +print("\nPlayer 1 scores...") +demoGame = TennisScorer.awardPoint(to: 1, game: demoGame) +ScoreDisplay.show(demoGame) + +print("\nPlayer 1 scores again...") +demoGame = TennisScorer.awardPoint(to: 1, game: demoGame) +ScoreDisplay.show(demoGame) + +// Player 2 catches up +print("\nPlayer 2 scores...") +demoGame = TennisScorer.awardPoint(to: 2, game: demoGame) +ScoreDisplay.show(demoGame) + +print("\nPlayer 2 scores again...") +demoGame = TennisScorer.awardPoint(to: 2, game: demoGame) +ScoreDisplay.show(demoGame) + +// Both reach 40 - deuce! +print("\nPlayer 1 scores (gets to 40)...") +demoGame = TennisScorer.awardPoint(to: 1, game: demoGame) +ScoreDisplay.show(demoGame) + +print("\nPlayer 2 scores (also gets to 40 - DEUCE!)...") +demoGame = TennisScorer.awardPoint(to: 2, game: demoGame) +ScoreDisplay.show(demoGame) + +// Advantage and win +print("\nPlayer 1 scores from deuce (gets advantage)...") +demoGame = TennisScorer.awardPoint(to: 1, game: demoGame) +ScoreDisplay.show(demoGame) + +print("\nPlayer 1 scores again (WINS!)...") +demoGame = TennisScorer.awardPoint(to: 1, game: demoGame) +ScoreDisplay.show(demoGame) + +// ๐Ÿ“‹ Simple test helper +func checkEqual(_ actual: T, _ expected: T, _ message: String = "") { + if actual == expected { + print(" โœ… \(message)") + } else { + print(" โŒ \(message)") + print(" Expected: \(expected), Got: \(actual)") + } +} diff --git a/examples/swift/Tennis2.playground/contents.xcplayground b/examples/swift/Tennis2.playground/contents.xcplayground new file mode 100644 index 0000000..7f43ce8 --- /dev/null +++ b/examples/swift/Tennis2.playground/contents.xcplayground @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/examples/swift/Tennis2.playground/playground.xcworkspace/contents.xcworkspacedata b/examples/swift/Tennis2.playground/playground.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..ca3329e --- /dev/null +++ b/examples/swift/Tennis2.playground/playground.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/swift/Tennis2.playground/playground.xcworkspace/xcuserdata/henryallsuch.xcuserdatad/UserInterfaceState.xcuserstate b/examples/swift/Tennis2.playground/playground.xcworkspace/xcuserdata/henryallsuch.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..27c864e Binary files /dev/null and b/examples/swift/Tennis2.playground/playground.xcworkspace/xcuserdata/henryallsuch.xcuserdatad/UserInterfaceState.xcuserstate differ