首页 > 代码库 > 用swift重写stanford CS193P的纸牌游戏 (2)- PlayingCard,PlayingDeck和ViewCotronller
用swift重写stanford CS193P的纸牌游戏 (2)- PlayingCard,PlayingDeck和ViewCotronller
本系列编号基本对应stanford CS193P的课程编号,可能有一两节课的误差:比如我标(1)就对应Lecture 1,但有时我做得快了就变成(1)对应lecture 1的全部和lecture 2的一部分。
正文:
本文包括三个类,PlayingCard和PlayingDeck,两个单元测试类,和两个单元测试的用到的工具类。
写完之后的感觉:只要写完view controller这个程序就能在iphone上有个用户界面可以点了,刚做出来的时候还是挺有成就感的。在看swift的电子书只看了一两章的时候我就想把这个做出来,但是没写对。
后来在网上一搜发现别人也有在做这个,而且也在问这个怎么写。然后我借鉴他贴的代码把这个写出来了。再后面把书又读了几章,才决定再次重新开始继续看这个CS193P的课程,这次总算能跟上进度了。
具体值得记录的点有:
1.每个类都可以选TargetMembership,单元测试类应该只选CardGameTest,待测类应该同时选CardGame和CardGameTest。CardGame是我的应用名字。
选对之后我就不用一直用默认提供的CardGameTests这个类了,我后面写了两个分开的单元测试类
2.PlayingCard类里,suit和rank做了合法值校验,比如rank不能大于13,suit不能取四种花色以外的值。
这点在CS193P里,OC下用的做法是重载了这两个变量的setter,我的做法是用了swift的did set功能来校验。
3.PlayingCardTests是我写来测试PlayingCard的。原CS193P的老外是不写单元测试的。但我现在主职业是做测试的,所以我写了单元测试。
思路是一点一点校验PlayingCard类提供的所有东西,包括suit和rank的默认值或最大值,单个变量成功的设置、不成功的设置时的校验、
4.PlayingCardDeckTests里我通过检查deck里所有元素来检查待测的init方法。
并且在其后做了2次抽牌,抽牌之前先记录当前卡组,做随机抽牌,抽牌之后先用正则表达式证明抽到的牌在抽牌之前在卡组中,再用正则表达式判断被抽出的牌在抽牌之后不再在卡组中。
5.DeckPrintHelper只是用来打印当前卡组中的所有牌和打印随机抽出的牌,为了减少重复代码用。Regex是我从网上找来的swift正则表达式封装好的工具类
代码:
首先是ViewController,里面只有一个IBOutlet和一个IBAction
1 // 2 // ViewController.swift 3 // CardGame 4 // 5 // Created by colin.zt on 14-6-6. 6 // Copyright (c) 2014 Ting. All rights reserved. 7 // 8 9 import UIKit10 11 class ViewController: UIViewController {12 13 14 @IBOutlet var flipCount : UILabel15 16 var flipCountNum: Int = 017 override func viewDidLoad() {18 super.viewDidLoad()19 // Do any additional setup after loading the view, typically from a nib.20 }21 22 override func didReceiveMemoryWarning() {23 super.didReceiveMemoryWarning()24 25 // Dispose of any resources that can be recreated.26 }27 28 @IBAction func touchButton(sender : UIButton) {29 //check the card title to know which side is upside30 //then flip it by change the background image and title31 if (sender.currentTitle.isEmpty){32 sender.setTitle("A♣?", forState: UIControlState.Normal)33 sender.setBackgroundImage(UIImage(named:"CardFront"),forState: UIControlState.Normal)34 } else {35 sender.setTitle("", forState: UIControlState.Normal)36 sender.setBackgroundImage(UIImage(named:"CardBack"),forState: UIControlState.Normal)37 }38 //increse the flip count number39 flipCountNum++40 //update the flip count on screen41 setFlipCount()42 }43 44 func setFlipCount(){45 // update the flip count by reset the text of the label46 flipCount.text = "Flips:" + String(flipCountNum)47 48 }49 50 51 }
然后是PlayingCard类,他是Card类的子类
1 // 2 // PlayingCard.swift 3 // CardGame 4 // 5 // Created by colin.zt on 14-10-18. 6 // Copyright (c) 2014 Ting. All rights reserved. 7 // 8 9 import UIKit10 11 class PlayingCard: Card {12 //in did set, do a check and make it can‘t set to invalid value13 var suit: String = "?" {14 didSet {15 //#####could improve or not: is there a simpler way to check if an element is in an array?16 for it in PlayingCard.valid_suits{17 if it == suit{18 return19 }20 }21 suit = oldValue22 }23 }24 //in did set, do a check and make it can‘t set to invalid value25 var rank: Int = 0 {26 didSet {27 if rank > PlayingCard.max_rank{28 rank = oldValue29 }30 31 }32 }33 34 //all valid ranks in this game,"?" means unset35 class var rank_strings:String[] {36 return ["?","A","2","3","4","5","6","7","8","9","10","J","Q","K"]37 }38 39 //define the max rank40 class var max_rank:Int{41 return rank_strings.count - 142 }43 44 //all valid suits45 class var valid_suits:String[]{46 return ["♥?","♦?","♠?","♣?"]47 }48 49 init() {}50 51 //just pirnt the rank and suit, and rank 11 will be rank "J",and so n52 func contents() -> String{53 return PlayingCard.rank_strings[self.rank]+self.suit54 }55 56 57 }
接着是PlayingCardDeck类,他是Deck类的子类
1 // 2 // PlayingCardDeck.swift 3 // CardGame 4 // 5 // Created by colin.zt on 14-10-20. 6 // Copyright (c) 2014 Ting. All rights reserved. 7 // 8 9 import UIKit10 11 class PlayingCardDeck: Deck {12 //just init the deck with 52 cards13 init(){14 super.init()15 for suit in PlayingCard.valid_suits{16 for (var rank = 1; rank <= PlayingCard.max_rank; rank++){17 var card: PlayingCard = PlayingCard()18 card.rank = rank19 card.suit = suit20 cards.append(card)21 }22 }23 24 }25 26 }
为了测试这两个类,写了两个单元测试:
PlayingCardTests
1 // 2 // PlayingCardTests.swift 3 // CardGame 4 // 5 // Created by colin.zt on 14-10-18. 6 // Copyright (c) 2014 Ting. All rights reserved. 7 // 8 9 import XCTest10 11 class PlayingCardTests: XCTestCase {12 13 override func setUp() {14 super.setUp()15 // Put setup code here. This method is called before the invocation of each test method in the class.16 }17 18 override func tearDown() {19 // Put teardown code here. This method is called after the invocation of each test method in the class.20 super.tearDown()21 }22 23 func testExample() {24 // This is an example of a functional test case.25 var p_card: PlayingCard = PlayingCard()26 27 println(PlayingCard.valid_suits)28 println(PlayingCard.rank_strings)29 println(PlayingCard.max_rank)30 31 32 XCTAssert(PlayingCard.valid_suits == ["♥?","♦?","♠?","♣?"], "Pass")33 XCTAssert(PlayingCard.rank_strings == ["?","A","2","3","4","5","6","7","8","9","10","J","Q","K"], "Pass")34 XCTAssert(PlayingCard.max_rank == 13, "Pass")35 36 XCTAssert(p_card.contents == "??","Pass")37 println(p_card.contents)38 39 p_card.suit = "♥?"40 p_card.rank = 1141 println("this card:\(p_card.contents)")42 XCTAssert(p_card.contents == "J♥?","Pass")43 44 p_card.suit = "♀" // try to set an invalid value, it will be ignored because I do the check in didSet of the suit45 println(p_card.contents)46 XCTAssert(p_card.contents == "J♥?","Pass")47 48 49 p_card.suit = "♣?"50 println(p_card.contents)51 XCTAssert(p_card.contents == "J♣?","Pass")52 53 p_card.rank = 3354 println(p_card.contents)55 XCTAssert(p_card.contents == "J♣?","Pass")56 57 p_card.rank = 1358 println(p_card.contents)59 XCTAssert(p_card.contents == "K♣?","Pass")60 61 }62 63 func testPerformanceExample() {64 // This is an example of a performance test case.65 self.measureBlock() {66 // Put the code you want to measure the time of here.67 }68 }69 70 }
PlayingCardDeckTests
1 // 2 // PlayingCardDeckTests.swift 3 // CardGame 4 // 5 // Created by colin.zt on 14-10-20. 6 // Copyright (c) 2014 Ting. All rights reserved. 7 // 8 9 import XCTest10 11 class PlayingCardDeckTests: XCTestCase {12 13 override func setUp() {14 super.setUp()15 // Put setup code here. This method is called before the invocation of each test method in the class.16 }17 18 override func tearDown() {19 // Put teardown code here. This method is called after the invocation of each test method in the class.20 super.tearDown()21 }22 23 func testExample() {24 // This is an example of a functional test case.25 26 //to test the init method, I create a new deck27 var card_deck: PlayingCardDeck = PlayingCardDeck()28 29 // the expected result30 var expected_card_string: NSString = "A♥?,2♥?,3♥?,4♥?,5♥?,6♥?,7♥?,8♥?,9♥?,10♥?,J♥?,Q♥?,K♥?,A♦?,2♦?,3♦?,4♦?,5♦?,6♦?,7♦?,8♦?,9♦?,10♦?,J♦?,Q♦?,K♦?,A♠?,2♠?,3♠?,4♠?,5♠?,6♠?,7♠?,8♠?,9♠?,10♠?,J♠?,Q♠?,K♠?,A♣?,2♣?,3♣?,4♣?,5♣?,6♣?,7♣?,8♣?,9♣?,10♣?,J♣?,Q♣?,K♣?,"31 32 //print and check the deck, if it has all 52 cards33 XCTAssert( DeckPrintHelper.printDeck(card_deck) == expected_card_string,"Pass" )34 35 //check the card number in the deck36 XCTAssert(card_deck.cards.count == 52, "Pass")37 38 //draw a card and check again39 var draw_this_card = DeckPrintHelper.drawRandomCard(card_deck)40 // Regex("K♥?").is_found_in(DeckPrintHelper.printDeck(card_deck))41 // Regex(",").is_found_in(DeckPrintHelper.printDeck(card_deck))42 XCTAssert( Regex(draw_this_card.contents).is_found_in(expected_card_string),"Pass" )43 // use regex to check if still contain this card44 var after_draw_once = DeckPrintHelper.printDeck(card_deck)45 XCTAssert( Regex(draw_this_card.contents).is_found_in(after_draw_once) == false,"Pass" )46 XCTAssert(card_deck.cards.count == 51, "Pass")47 48 //do it again49 draw_this_card = DeckPrintHelper.drawRandomCard(card_deck)50 XCTAssert( Regex(draw_this_card.contents).is_found_in(after_draw_once),"Pass" )51 XCTAssert( Regex(draw_this_card.contents).is_found_in(DeckPrintHelper.printDeck(card_deck)) == false,"Pass" )52 XCTAssert(card_deck.cards.count == 50, "Pass")53 54 55 }56 57 58 59 func testPerformanceExample() {60 // This is an example of a performance test case.61 self.measureBlock() {62 // Put the code you want to measure the time of here.63 }64 }65 66 }
其中有写打印所有card的方法写在了工具类里
DeckPrintHelper
1 // 2 // DeckPrintHelper.swift 3 // CardGame 4 // 5 // Created by colin.zt on 14-10-20. 6 // Copyright (c) 2014 Ting. All rights reserved. 7 // 8 9 //a help class which contains some test logic for reuse10 import UIKit11 class DeckPrintHelper: NSObject {12 13 // to print and return the deck string in my format14 class func printDeck(deck: Deck) -> String{15 var card_string: String = ""16 println("\n_______________________________________")17 println("Now these cards in deck")18 for card in deck.cards{19 card_string += ("\(card.contents),")20 }21 println(card_string)22 println("_______________________________________")23 24 return card_string25 }26 27 // to draw a random card while printing some comments28 class func drawRandomCard(deck: Deck) -> Card{29 var random_card = deck.drawRandomCard()30 println("Now draw this random card:")31 println(random_card.contents)32 return random_card33 }34 }
还有一个正则表达式的工具类
Regex
1 // 2 // Regec.swift 3 // CardGame 4 // 5 // Created by colin.zt on 14-10-20. 6 // Copyright (c) 2014年 Ting. All rights reserved. 7 // 8 9 import Foundation10 11 class Regex: NSObject {12 let internalExpression: NSRegularExpression13 let pattern: String14 15 init(_ pattern: String) {16 self.pattern = pattern17 var error: NSError?18 self.internalExpression = NSRegularExpression(pattern: pattern, options: .CaseInsensitive, error: &error)19 }20 21 func is_found_in(input: String) -> Bool {22 let matches = self.internalExpression.matchesInString(input, options: nil, range:NSMakeRange(0, countElements(input)))23 var count = matches.count24 return matches.count > 025 }26 }27 28 //Usage Example: Regex("\\w{4}").is_found_in("abdddcd")
用swift重写stanford CS193P的纸牌游戏 (2)- PlayingCard,PlayingDeck和ViewCotronller