Skip to content

Commit 1132491

Browse files
committed
Rough in a new code example.
This approach doesn't rely (directly or indirectly) on existential types. The old example used two protocols at the same scope, in part because protocol nesting wasn't supported when this was first written. That's no longer needed -- we can now show a delegate protocol nested inside the type it supports.
1 parent 659ee33 commit 1132491

File tree

1 file changed

+70
-166
lines changed

1 file changed

+70
-166
lines changed

TSPL.docc/LanguageGuide/Protocols.md

Lines changed: 70 additions & 166 deletions
Original file line numberDiff line numberDiff line change
@@ -826,47 +826,55 @@ the underlying type of that source.
826826
The example below defines two protocols for use with dice-based board games:
827827

828828
```swift
829-
protocol DiceGame {
830-
var dice: Dice { get }
831-
func play()
829+
class DiceGame {
830+
let sides: Int
831+
let generator = LinearCongruentialGenerator()
832+
weak var delegate: Delegate?
833+
834+
init(sides: Int) {
835+
self.sides = sides
836+
}
837+
838+
func roll() -> Int {
839+
return Int(generator.random() * Double(sides)) + 1
840+
}
841+
842+
func play(rounds: Int) {
843+
delegate?.gameDidStart(self)
844+
for round in 1...rounds {
845+
let player1 = roll()
846+
let player2 = roll()
847+
if player1 == player2 {
848+
delegate?.game(self, didEndRound: round, winner: nil)
849+
} else if player1 > player2 {
850+
delegate?.game(self, didEndRound: round, winner: 1)
851+
} else {
852+
delegate?.game(self, didEndRound: round, winner: 2)
853+
}
854+
}
855+
delegate?.gameDidEnd(self)
856+
}
832857

833858
protocol Delegate: AnyObject {
834859
func gameDidStart(_ game: DiceGame)
835-
func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int)
860+
func game(_ game: DiceGame, didEndRound round: Int, winner: Int?)
836861
func gameDidEnd(_ game: DiceGame)
837862
}
838863
}
839864
```
840865

841-
<!--
842-
- test: `protocols`
843-
844-
```swifttest
845-
-> protocol DiceGame {
846-
var dice: Dice { get }
847-
func play()
848-
}
849-
-> protocol DiceGameDelegate: AnyObject {
850-
func gameDidStart(_ game: DiceGame)
851-
func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int)
852-
func gameDidEnd(_ game: DiceGame)
853-
}
854-
```
855-
-->
856-
857-
The `DiceGame` protocol is a protocol that can be adopted
858-
by any game that involves dice.
859-
866+
The `DiceGame` class implements a simple game using dice.
860867
The `DiceGame.Delegate` protocol can be adopted
861-
to track the progress of a type that implements `DiceGame`.
868+
to track the progress of this game.
862869
Because the `DiceGame.Delegate` protocol
863870
is always used in the context of a dice game,
864871
it's nested inside of the `DiceGame` protocol.
865-
Protocols can be nested inside of other protocols
866-
and inside of type declarations like structures and classes,
872+
Protocols can be nested
873+
inside of type declarations like structures and classes,
867874
as long as the outer declaration isn't generic.
868875
For information about nesting types, see <doc:NestedTypes>.
869876

877+
<!-- XXX delete forward reference? -->
870878
To prevent strong reference cycles,
871879
delegates are declared as weak references.
872880
For information about weak references,
@@ -878,84 +886,7 @@ A class-only protocol
878886
is marked by its inheritance from `AnyObject`,
879887
as discussed in <doc:Protocols#Class-Only-Protocols>.
880888

881-
Here's a version of the *Snakes and Ladders* game originally introduced in <doc:ControlFlow>.
882-
This version is adapted to use a `Dice` instance for its dice-rolls;
883-
to adopt the `DiceGame` protocol;
884-
and to notify a `DiceGame.Delegate` about its progress:
885-
886-
```swift
887-
class SnakesAndLadders: DiceGame {
888-
let finalSquare = 25
889-
let dice = Dice(sides: 6, generator: LinearCongruentialGenerator())
890-
var square = 0
891-
var board: [Int]
892-
init() {
893-
board = Array(repeating: 0, count: finalSquare + 1)
894-
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
895-
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
896-
}
897-
weak var delegate: DiceGame.Delegate?
898-
func play() {
899-
square = 0
900-
delegate?.gameDidStart(self)
901-
gameLoop: while square != finalSquare {
902-
let diceRoll = dice.roll()
903-
delegate?.game(self, didStartNewTurnWithDiceRoll: diceRoll)
904-
switch square + diceRoll {
905-
case finalSquare:
906-
break gameLoop
907-
case let newSquare where newSquare > finalSquare:
908-
continue gameLoop
909-
default:
910-
square += diceRoll
911-
square += board[square]
912-
}
913-
}
914-
delegate?.gameDidEnd(self)
915-
}
916-
}
917-
```
918-
919-
<!--
920-
- test: `protocols`
921-
922-
```swifttest
923-
-> class SnakesAndLadders: DiceGame {
924-
let finalSquare = 25
925-
let dice = Dice(sides: 6, generator: LinearCongruentialGenerator())
926-
var square = 0
927-
var board: [Int]
928-
init() {
929-
board = Array(repeating: 0, count: finalSquare + 1)
930-
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
931-
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
932-
}
933-
weak var delegate: DiceGame.Delegate?
934-
func play() {
935-
square = 0
936-
delegate?.gameDidStart(self)
937-
gameLoop: while square != finalSquare {
938-
let diceRoll = dice.roll()
939-
delegate?.game(self, didStartNewTurnWithDiceRoll: diceRoll)
940-
switch square + diceRoll {
941-
case finalSquare:
942-
break gameLoop
943-
case let newSquare where newSquare > finalSquare:
944-
continue gameLoop
945-
default:
946-
square += diceRoll
947-
square += board[square]
948-
}
949-
}
950-
delegate?.gameDidEnd(self)
951-
}
952-
}
953-
```
954-
-->
955-
956-
For a description of the *Snakes and Ladders* gameplay,
957-
see <doc:ControlFlow#Break>.
958-
889+
<!-- XXX rewrite paragraph -->
959890
This version of the game is wrapped up as a class called `SnakesAndLadders`,
960891
which adopts the `DiceGame` protocol.
961892
It provides a gettable `dice` property and a `play()` method
@@ -964,11 +895,13 @@ in order to conform to the protocol.
964895
because it doesn't need to change after initialization,
965896
and the protocol only requires that it must be gettable.)
966897

898+
<!-- XXX rewrite paragraph -->
967899
The *Snakes and Ladders* game board setup takes place within
968900
the class's `init()` initializer.
969901
All game logic is moved into the protocol's `play` method,
970902
which uses the protocol's required `dice` property to provide its dice roll values.
971903

904+
<!-- XXX rewrite paragraph -->
972905
Note that the `delegate` property is defined as an *optional* `DiceGame.Delegate`,
973906
because a delegate isn't required in order to play the game.
974907
Because it's of an optional type,
@@ -988,7 +921,7 @@ If the `delegate` property is nil,
988921
these delegate calls fail gracefully and without error.
989922
If the `delegate` property is non-nil,
990923
the delegate methods are called,
991-
and are passed the `SnakesAndLadders` instance as a parameter.
924+
and are passed the `DiceGame` instance as a parameter.
992925

993926
<!--
994927
TODO: add a cross-reference to optional chaining here.
@@ -999,54 +932,44 @@ which adopts the `DiceGame.Delegate` protocol:
999932

1000933
```swift
1001934
class DiceGameTracker: DiceGame.Delegate {
1002-
var numberOfTurns = 0
935+
var playerScore1 = 0
936+
var playerScore2 = 0
1003937
func gameDidStart(_ game: DiceGame) {
1004-
numberOfTurns = 0
1005-
if game is SnakesAndLadders {
1006-
print("Started a new game of Snakes and Ladders")
1007-
}
1008-
print("The game is using a \(game.dice.sides)-sided dice")
938+
print("Started a new game")
939+
playerScore1 = 0
940+
playerScore2 = 0
1009941
}
1010-
func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) {
1011-
numberOfTurns += 1
1012-
print("Rolled a \(diceRoll)")
942+
func game(_ game: DiceGame, didEndRound round: Int, winner: Int?) {
943+
switch winner {
944+
case 1:
945+
playerScore1 += 1
946+
print("Player 1 won round \(round)")
947+
case 2: playerScore2 += 1
948+
print("Player 2 won round \(round)")
949+
default:
950+
print("Round was a draw")
951+
}
1013952
}
1014953
func gameDidEnd(_ game: DiceGame) {
1015-
print("The game lasted for \(numberOfTurns) turns")
954+
if playerScore1 == playerScore2 {
955+
print("The game ended in a draw.")
956+
} else if playerScore1 > playerScore2 {
957+
print("Player 1 won!")
958+
} else {
959+
print("Player 2 won!")
960+
}
1016961
}
1017962
}
1018963
```
1019964

1020-
<!--
1021-
- test: `protocols`
1022-
1023-
```swifttest
1024-
-> class DiceGameTracker: DiceGame.Delegate {
1025-
var numberOfTurns = 0
1026-
func gameDidStart(_ game: DiceGame) {
1027-
numberOfTurns = 0
1028-
if game is SnakesAndLadders {
1029-
print("Started a new game of Snakes and Ladders")
1030-
}
1031-
print("The game is using a \(game.dice.sides)-sided dice")
1032-
}
1033-
func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) {
1034-
numberOfTurns += 1
1035-
print("Rolled a \(diceRoll)")
1036-
}
1037-
func gameDidEnd(_ game: DiceGame) {
1038-
print("The game lasted for \(numberOfTurns) turns")
1039-
}
1040-
}
1041-
```
1042-
-->
1043-
965+
<!-- XXX rewrite paragraph -->
1044966
`DiceGameTracker` implements all three methods required by `DiceGame.Delegate`.
1045967
It uses these methods to keep track of the number of turns a game has taken.
1046968
It resets a `numberOfTurns` property to zero when the game starts,
1047969
increments it each time a new turn begins,
1048970
and prints out the total number of turns once the game has ended.
1049971

972+
<!-- XXX rewrite paragraph -->
1050973
The implementation of `gameDidStart(_:)` shown above uses the `game` parameter
1051974
to print some introductory information about the game that's about to be played.
1052975
The `game` parameter has a type of `DiceGame`, not `SnakesAndLadders`,
@@ -1058,6 +981,7 @@ In this example, it checks whether `game` is actually
1058981
an instance of `SnakesAndLadders` behind the scenes,
1059982
and prints an appropriate message if so.
1060983

984+
<!-- XXX rewrite paragraph -->
1061985
The `gameDidStart(_:)` method also accesses the `dice` property of the passed `game` parameter.
1062986
Because `game` is known to conform to the `DiceGame` protocol,
1063987
it's guaranteed to have a `dice` property,
@@ -1068,36 +992,16 @@ Here's how `DiceGameTracker` looks in action:
1068992

1069993
```swift
1070994
let tracker = DiceGameTracker()
1071-
let game = SnakesAndLadders()
995+
let game = DiceGame(sides: 6)
1072996
game.delegate = tracker
1073-
game.play()
1074-
// Started a new game of Snakes and Ladders
1075-
// The game is using a 6-sided dice
1076-
// Rolled a 3
1077-
// Rolled a 5
1078-
// Rolled a 4
1079-
// Rolled a 5
1080-
// The game lasted for 4 turns
997+
game.play(rounds: 3)
998+
// Started a new game
999+
// Player 2 won round 1
1000+
// Player 2 won round 2
1001+
// Player 1 won round 3
1002+
// Player 2 won!
10811003
```
10821004

1083-
<!--
1084-
- test: `protocols`
1085-
1086-
```swifttest
1087-
-> let tracker = DiceGameTracker()
1088-
-> let game = SnakesAndLadders()
1089-
-> game.delegate = tracker
1090-
-> game.play()
1091-
</ Started a new game of Snakes and Ladders
1092-
</ The game is using a 6-sided dice
1093-
</ Rolled a 3
1094-
</ Rolled a 5
1095-
</ Rolled a 4
1096-
</ Rolled a 5
1097-
</ The game lasted for 4 turns
1098-
```
1099-
-->
1100-
11011005
## Adding Protocol Conformance with an Extension
11021006

11031007
You can extend an existing type to adopt and conform to a new protocol,

0 commit comments

Comments
 (0)