tachikoma_ai を v0.3.0 にアップデートしました #mokumoku_onsen

7/16(土)〜18(月)の2泊3日で開発合宿に参加していて、前々からやりたかった tachikoma_ai の gem のアップデートをしました。

開発合宿について、詳しくは id:niwatako さんが記事を書かれているので、興味ある方はそちらを参照して下さい。

niwatako.hatenablog.jp

TachikomaAi について

TachikomaAi は sanemat/tachikoma の拡張で、 bundle update のときプルリクに github の比較URLをつける gem です。

f:id:sinsoku:20160229024048p:plain

実は tachikoma_ai を仕事でも使っているのですが、使っていて機能不足なところやバグを見つけたので、今回直しました。

変更内容

v0.2.0

  • 0.1.0 のような v のついてないタグに対応

v0.3.0

.gemspec の対応について

.gemspec の homepage 以外で github.com の URL を探す方法が見つからなかったので、 json ファイルでリンクを管理して対応しました。
もし TachikomaAi を使っている人で、対応 gem を増やしたい人は json にリンクを追加してプルリクください。

github 指定について

vendor/bundle/ruby/2.3.0/bundler/gems/ の下の .gemspec を使えば対応できそうだけど、ちょっと対応に時間かかりそう。
また時間のあるときに対応したい。

これで来週からお仕事での gem のアップデート確認が少し楽になる(∩´∀`)∩

Swift でトランプを実装しようとして、勉強になってる話

まだ全然実装できていないけど、とても勉強になっているので途中経過をブログに書いてみる。

github.com

なぜやっているのか

トランプくらい簡単に実装できる・・・そう思っていた時期が(ry

環境

この環境で勉強を始めたのが間違いだったかもしれない。

やりたいこと

  • 汎用的にトランプのゲームを作れるようなライブラリ設計にする
  • ポーカー、七並べ、大富豪など、メジャーなゲームを実装する

設計など

  • Card が Rank ( 1 〜 13 ) と Suit (スペード、ハート、クラブ、ダイヤ) を持つ
  • Card の強弱をゲームによって変える
    • Generics で変更できるようにした
  • まだ Jocker は未実装

Swift で実装方法が分からなかった箇所など

stringLiteralConvertible で特定文字だけ許容したい

let card: Card<DefaultComparing> = "13S"

これで「スペード13」が生成したかった。結局、できなかったので、 init? を使った。

https://github.com/sinsoku/PlayingCard/blob/908faee6fe4fe1d66e028ee481b6ad07c06f4322/Sources/PlayingCard/Card.swift#L14-L33

init?(_ value: String) {
  var characters = value.characters
  let suit = characters.popLast()
  let rank = Int(String(characters))

  guard let rankInt = rank where 1...13 ~= rankInt else {
    return nil
  }

  guard let suitStr = suit where ["S", "H", "C", "D"].contains(suitStr) else {
    return nil
  }

  switch suitStr {
    case "S": self = Card<T>(rank: Rank(rawValue: rankInt)!, suit: .Spade)
    case "H": self = Card<T>(rank: Rank(rawValue: rankInt)!, suit: .Heart)
    case "C": self = Card<T>(rank: Rank(rawValue: rankInt)!, suit: .Club)
    case "D": self = Card<T>(rank: Rank(rawValue: rankInt)!, suit: .Diamond)
    default: return nil
  }
}

無理矢理感がある・・・。
もうちょい綺麗になりそうだけど、よく分からんかったので他の実装を進めている。

戻り値を Set, Array で切り替えたい

https://github.com/sinsoku/PlayingCard/blob/908faee6fe4fe1d66e028ee481b6ad07c06f4322/Sources/PlayingCard/Util.swift

class Util {
    static func factory<C: CardInitializeable>(_ values: String...) -> Set<C> {
      let cards = values.map { C($0)! }
      return Set(cards)
    }
}

戻り値も Generics でうまく返したかったけど、よく分からなかった。

let arrCards: Array<Card<DefaultComparing>> = Util.factory("1S", "2S", "3S", "4S", "5S")
let setCards: Set<Card<DefaultComparing>> = Util.factory("1S", "2S", "3S", "4S", "5S")

本当は ArrayLiteral で定義できると綺麗なんだけど、これ init? だから出来ないんですよね・・・。

let setCards: Set<Card<DefaultComparing>> = ["1S", "2S", "3S", "4S", "5S"]

Swift で Generics を使って Comparable の挙動を変更する

昨日の続きを調べて、一応やりたかったことは実現できたのでブログにも書いておく。

sinsoku.hatenablog.com

サンプルコード

gist のコードの通りだけど、 Generics を使ってクラスの初期化時に Comparable に使う struct を渡せるようにしている。 こうやっておけば let array = [A<Q>()] みたいに同じ Comparable の型に限定した Array が作れて型安全。

WIP Pull Request Unhighlignter for GitHub が使えなくなったので Tampermonkey の UserScript で再実装した

WIP Pull Request Unhighlignter for GitHub が便利で今まで使っていたけど、昨日突然使えなくなった。

※ 原因はGitHubCSSセレクタが変わったため。現在は kyanny/chrome-ext-wip-pull-request-unhighlighter-for-github#6 がマージされ動くようになってる。

再実装した

待っていれば直ると思ったけど、予想以上に無いと困ったので最低限の機能を Tampermonkey の UserScript として再実装した。 ついでに、 pjax で画面遷移した時も動くようにした。

本当はプルリク投げた方が良いかなーと思いつつ、CSSセレクタの指定は好みある*1だろうし、テストコードも無かったので面倒で止めた。

*1:個人的にはclass指定よりidの方が壊れにくいと思うけど、本家はclass使っている

Swift の Mirror API を触って、動作を XCTest で確認した

今日やってた勉強内容。

github.com

動かし方

$ git clone https://github.com/sinsoku/study.git
$ cd study/swift
$ bin/run swift build -C PlayGround
$ bin/run swift test -C PlayGround

ソースコード

// PlayGround/Sources/PlayGround/SimpleClass.swift
class BaseClass {
  let number: Int = 0
  let string: String? = nil
}

class SimpleClass: BaseClass {
}

テストコード

// PlayGround/Tests/PlayGround/SimpleClassTests.swift
import XCTest
@testable import PlayGround

class SimpleClassTests: XCTestCase {
    func testSimpleClassBehavior() {
        let obj = SimpleClass()
        // refs: https://stackoverflow.com/questions/25725033/swift-too-smart-checking-an-objects-type-when-testing-with-xctest
        XCTAssertTrue((obj as Any) is SimpleClass)
        XCTAssertTrue((obj as Any) is BaseClass)
    }

    // refs: https://github.com/apple/swift/blob/swift-DEVELOPMENT-SNAPSHOT-2016-06-06-a/stdlib/public/core/Mirror.swift#L137
    func testReflectionBehavior() {
        let playGround = Mirror(reflecting: PlayGround())
        XCTAssertEqual(playGround.description, "Mirror for PlayGround")
        XCTAssertEqual(playGround.displayStyle, Mirror.DisplayStyle.struct)
        XCTAssertTrue(playGround.subjectType == PlayGround.self)

        let base = Mirror(reflecting: BaseClass())
        XCTAssertEqual(base.description, "Mirror for BaseClass")
        XCTAssertEqual(base.displayStyle, Mirror.DisplayStyle.class)
        XCTAssertTrue(base.subjectType == BaseClass.self)

        let simple = Mirror(reflecting: SimpleClass())
        XCTAssertEqual(simple.description, "Mirror for SimpleClass")
        XCTAssertEqual(simple.displayStyle, Mirror.DisplayStyle.class)
        XCTAssertTrue(simple.subjectType == SimpleClass.self)

        let parent = simple.superclassMirror
        XCTAssertEqual(parent?.description, "Mirror for BaseClass")
        XCTAssertEqual(parent?.displayStyle, Mirror.DisplayStyle.class)
        XCTAssertTrue(parent?.subjectType == BaseClass.self)

        let defNumber = base.children.filter { $0.label == "number" }[0]
        XCTAssertEqual((defNumber.value as! Int), 0)
        let defString = base.children.filter { $0.label == "string" }[0]
        // FIXME: the assertion is failed. (XCTAssertNil failed: "nil")
        // XCTAssertNil(defString.value)
        XCTAssertEqual(String(defString.value), "nil")
    }

    static var allTests : [(String, (SimpleClassTests) -> () throws -> Void)] {
        return [
            ("testSimpleClassBehavior", testSimpleClassBehavior),
            ("testReflectionBehavior", testReflectionBehavior),
        ]
    }
}

FIXME について

value の型が Optional<String> にはなっているっぽいのに、なぜか nil にはならずにテストが通らなかった。 結局、よく分からないまま・・・。

XCode でも調べてみた

class Foo {
    let number: Int? = nil
}

let m = Mirror(reflecting: Foo())
let defNumber = m.children.filter { $0.label == "number" }[0]
defNumber.value == nil
// Playground execution failed: MyPlayground.playground:1:17: error: value of type 'Any' (aka 'protocol<>') can never be nil, comparison isn't allowed
// defNumber.value == nil
//~~~~~~~~~~~~~~~ ^

エラーでるけど、結局 value の返す値が何なのかが不明・・・。

参考にしたページ

XCode を使わず Swift で TDD しながら FizzBuzz を書いてみた

この前作った Swift の環境 で勉強として FizzBuzz を書いてみた。

github.com

パッケージの作り方

$ mkdir FizzBuzz
$ cd FizzBuzz
$ swift package init

コード

// Sources/app/FizzBuzz.swift
class FizzBuzz {
    let num : Int

    init(num : Int) {
        self.num = num
    }

    func toString() -> String {
        switch self.num {
            case let n where n % 15 == 0:
                return "FizzBuzz"
            case let n where n % 3 == 0:
                return "Fizz"
            case let n where n % 5 == 0:
                return "Buzz"
            default:
                return String(self.num)
        }
    }
}
// Tests/app/FizzBuzzTests.swift
import XCTest
@testable import app

class FizzBuzzTests: XCTestCase {
    func testThree() {
      let fizzBuzz = FizzBuzz(num: 3)
      XCTAssertEqual(fizzBuzz.toString(), "Fizz")
    }

    func testFive() {
      let fizzBuzz = FizzBuzz(num: 5)
      XCTAssertEqual(fizzBuzz.toString(), "Buzz")
    }

    func testFifteen() {
      let fizzBuzz = FizzBuzz(num: 15)
      XCTAssertEqual(fizzBuzz.toString(), "FizzBuzz")
    }

    func testOther() {
      let fizzBuzz = FizzBuzz(num: 1)
      XCTAssertEqual(fizzBuzz.toString(), "1")
    }

    static var allTests : [(String, (FizzBuzzTests) -> () throws -> Void)] {
        return [
            ("testThree", testThree),
            ("testFive", testFive),
            ("testFifteen", testFifteen),
            ("testOther", testOther)
        ]
    }
}

テストの実行方法

$ swift build
$ swift test