image

Привет, братья. Не скрою, меня часто терзают мысли. Что никакой я не программист, думаю линейно, предпочитаю легкое чтиво учебникам по промышленному производству софта. Все новое приводит меня либо в ужас, либо в тоску. Тем не менее, я решил поменять профессию и родину и получил по блату должность iOS-евангелист (это официально, я не шучу). Пришлось лечь под репозитории, юнит-тесты, и освоить язык Swift. Нет лучшего способа выучить новый язык, чем завести девчонку-носительницу этого языка. А где такую взять? Нет их, а если есть, то заняты молодыми и красивыми программистами, не мне чета. Другой способ обучения — написать что-то на новом языке, тем более, что идея для приложения у меня есть. Да конечно это игра, — простая, как Архимед в ванной.

В процессе творчества возникло несколько вопросов, которые нигде не засвечены, а думаю, хабрахабрахаб самое место для ответов на них.

И да, тот кто сказал, что программировать на Swift проще, чем на Objective-C, тот собака позорная.


Итак, нажимая кнопку File->New->Project...->Single View Application Вы получаете готовый проект-заготовку, главный класс приложения AppDelegate.swift выглядит пугающе просто

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    
    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        // Override point for customization after application launch.
    }
}  


Что изменилось? Во-первых, вместо двух файлов (как в С), стал один, как в Pascal. Это я одобряю.
Кроме того, в тексте появились странные символы? и!.. Не пугайтесь, это признаки типа переменной, если переменная x:Float! — то точно Float, если x:Float? — то автор не уверен, что данная переменная всегда будет Float.

Честно говоря, Xcode сам подставляет в случае нужды эти знаки, и после дня потного кодирования, Вы начнете понимать логику использования идиотских символов.

Реклама


Итак, что же необходимо для игры? Конечно, реклама. Именно в этот class AppDelegate ее надо добавлять. Скачиваем последнюю версию GoogleMobileAds со Свифтом, добавляем в проект и пишем пару функций

import UIKit
import GoogleMobileAds

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

  var window: UIWindow?
  var bannerView: GADBannerView!
  var iPhone5 = 1
        
    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
  
     startAds()
        
        return true
    }
    
    func startAds() {
        bannerView = GADBannerView(adSize: kGADAdSizeBanner)
        print("Google Mobile Ads SDK version: " + GADRequest.sdkVersion())
        bannerView.adUnitID = "ca-app-pub-here your ID-/8937549167"
        bannerView.rootViewController = self.window?.rootViewController
        let adViewHeight = bannerView.frame.size.height
        print("adViewHeight: \(adViewHeight)")
        let screenSize: CGRect = UIScreen.mainScreen().bounds
        let screenHeight = screenSize.height
        iPhone5 = screenHeight<500 ? 0 : 1
        bannerView.frame.origin = CGPointMake(0, screenHeight -  adViewHeight)
        let request = GADRequest()
        request.testDevices = [kGADSimulatorID ]
        self.window?.rootViewController!.view.addSubview(bannerView)
        bannerView.loadRequest(request)
    }
    
    func bringAdsToFront() {
        self.window?.rootViewController!.view.bringSubviewToFront(bannerView)
    }
    
}  


В теле основной функции появился вызов startAds().
В принципе, Вы научились программировать на Swift.

Функция bringAdsToFront() на самом деле мне не нужна, но Вам может пригодиться.
Переменная iPhone5 всегда необходима, как воздух, пока живы два Aspect Ratio в iOS.

Почему так? Приложение у меня, чтобы играть в метро одной рукой на телефоне, поэтому всегда рабочий режим — Портрет. Рекламу 320х50 я спускаю вниз экрана, это увеличивает заработки вдвое.

Звук


Что еще необходимо в игре, даже самой примитивной? Звуки Му.

Добавляем.

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

   var soundLib: SoundController?
    
    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        // Override point for customization after application launch.

      soundLib = SoundController.init()
   
   }

    func setVolume(v:Double) {
        soundLib?.setVolume(v*v)
    }
    
    func playSound(i:Int) {
        soundLib?.playSound(i)
    }

}  


В проект добавлем десяток mp3 файлов, вытащенных из Google, и создаем класс SoundController в отдельном файле SoundController.swift

//
// здесь ссылка на приложение в аппстор, качайте не пожалеете
//
//     https://itunes.apple.com/us/app/pow-2/id1093873891?ls=1&mt=8
//

import UIKit
import AVFoundation

class SoundController {
    var players : [AVAudioPlayer] = []
    var volume = 0.0
    init () {
        var a1 = self.setupAudioPlayerWithFile("Ashley_glad_1", type:"caf")    //  0
        players.append(a1!)
        a1 = self.setupAudioPlayerWithFile("star_first", type:"caf")           //  1
        players.append(a1!)
        a1 = self.setupAudioPlayerWithFile("star_third", type:"caf")           //  2
        players.append(a1!)
        a1 = self.setupAudioPlayerWithFile("bouncer", type:"caf")              //  3
        players.append(a1!)
        a1 = self.setupAudioPlayerWithFile("bird shot-a3", type:"mp3")         //  4
        players.append(a1!)
        a1 = self.setupAudioPlayerWithFile("ball_bounce", type:"mp3")          //  5
        players.append(a1!)
        a1 = self.setupAudioPlayerWithFile("bird_a4", type:"mp3")              //  6
        players.append(a1!)
        a1 = self.setupAudioPlayerWithFile("HittingBasketBoard", type:"mp3")   //  7
        players.append(a1!)
        a1 = self.setupAudioPlayerWithFile("piglette_a1", type:"mp3")          //  8
        players.append(a1!)
    }
  
    
    func setVolume(v:Double) {
        volume = v
        for audioPlayer in players {
            audioPlayer.volume = Float(volume)
        }
    }
    
    
    func playSound(i:Int)
    {
        let audioPlayer = players[i]
        audioPlayer.play()
    }
        
    
    func setupAudioPlayerWithFile(file:NSString, type:NSString) -> AVAudioPlayer?  {
        //1
        let path = NSBundle.mainBundle().pathForResource(file as String, ofType: type as String)
        let url = NSURL.fileURLWithPath(path!)
        
        //2
        var audioPlayer:AVAudioPlayer?
        
        // 3
        do {
            try audioPlayer = AVAudioPlayer(contentsOfURL: url)
            audioPlayer!.prepareToPlay()
            
        } catch {
            print("Player not available")
        }
        return audioPlayer
    }
}


Можно все сделать изящней, но на первый раз пусть будет надежно и тупо.

Теперь для проигрыша любого из десяти файлов в любом месте Вашей программы надо выполнить вызов.
    
  let app = UIApplication.sharedApplication().delegate as! AppDelegate
  app.playSound(0)
     


Настройки



В любой игре надо сохранять настройки и текущее состояние игры. Мало ли что, телефон разрядится, и клиент не должен потерять блестяще найденное решение при решении очередной головоломки.

Для этого в том же классе AppDelegate добавляем две функции

  func loadPrefs()
    {
        let pre = NSLocale.preferredLanguages()[0]
        if pre == "ru" {
            flag = 7
        }
        print("language = \(pre)  flag=\(flag)")

        
        let defaults = NSUserDefaults.standardUserDefaults()
        for i in 0...20 {
            let s = defaults.integerForKey("s_\(i)")
            status[i] = s%500
            a_best[i] = s/500
        }
    }
    

    
    func savePrefs()
    {
        let defaults = NSUserDefaults.standardUserDefaults()
        for i in 0...20 {
            let s = status[i] + 500 * a_best[i]
            defaults.setInteger(s, forKey: "s_\(i)")
        }
        defaults.synchronize()
    }


Нетрудно заметить, что поскольку я парень советский, то единственная локализация, которую я признаю и умею делать — русская. В теле функции loadPrefs() я определяю отечественный ли iPhone у уважаемого игрока, если да, то пишу ему гнусные шутки внутри приложения на нашем родном языке.

Так игра называется Pow 2, а в родном варианте почему-то Хрясь

Управляемый Счетчик Случайных Чисел


Нужен почти всегда. Причем я использую выдранный из MSVC 20 лет назад С-шный код, который адаптировал на все языки от JS до PHP, и он выдает одинаковые последовательности случайных чисел начиная с любого holdrand от 0 до конечности Int32

  var holdrand  = 0
    
    func microsoft_rnd()->Int {
        var k = Int.multiplyWithOverflow(holdrand, 214013)
        k = Int.addWithOverflow(k.0, 2531011)
        holdrand =  k.0
        return Int((holdrand >> 16)  & 0x7FFF)
    }
    
    
    func microsoft_rand(number:Int)->Int {
        return microsoft_rnd() % number
    }



Как видно, по сравнению с Obj-C пришлось изрядно повозиться — Swift не любит переполнения и строго за них наказывает.
Вызов функции в коде выглядит примерно так
    for  _ in 0..<2016 {
            let k =  microsoft_rand(25)  //  Int(arc4random()%25)
            let j = microsoft_rand(25)  //Int(arc4random()%25)
            
            if dots[k] > 0 && dots[j] > 0 {
                let d = dots[k]
                dots[k] = dots[j]
                dots[j] = d
            }
        }
 


Все остальное


Все остальное в моей игре стандартно и взято из документации по Swift.
Саму идею приложения и как она родилась расписывать не стану, модераторы меня беспощадно посылают в черновик.
Так что играйте, если нужна ссылка, в комментариях отвечу.

Самое удивительное, что статью я и не собирался публиковать. Просто сегодня произошло чудо. Я выложил игру после обеда в магазин на проверку и приготовился к долгому двухнедельному ожиданию. И вдруг, хлоп, вечером уже она одобрена. 6 часов на все! Даже андроидные ребята такого не видали.

Я тут же объявил ее бесплатной, накатал сей опус, и пожелал спокойной ночи братьям во свифте.

Комментарии (34)


  1. andrew8712
    26.03.2016 19:52

    "… если переменная x:Float! — то точно Float..."
    Ну, справедливости ради стоит сказать, что x в данном случае может быть и nil, и это может привести к проблемам на этапе выполнения. Можно словить «Unexpectedly found nil when unwrapping an optional value».


  1. igorch96
    26.03.2016 21:22

    Кстати, если я правильно помню, Apple рекомендует писать тескт программы, как прозу: без сокращений в именах переменных и функциях; подбирать имена так, чтобы было понятно, чем занимается программа из текста ее кода. Ну и желательно обходиться без комментариев, пусть код программы сам за себя говорит.


  1. akirsanov
    26.03.2016 21:41
    +1

    "я решил поменять профессию и родину и получил по блату должность iOS-евангелист (это официально, я не шучу)" — расскажите об этом, если не секрет.
    Вадима Башурова из Арзамаса-16 знает каждый второй программист, да к тому же Вы так замечательно пишите!


    1. PapaBubaDiop
      27.03.2016 01:45

      Изменить Родине в наше время так легко, купил билет и, через 3 с половиной часа, ты — предатель. Самое тяжелое — не умение быть собой на чужом языке. Через год напишу, когда контракт закончится…