Обучающая текстовая игра на Swift для macOS. Создана, чтобы помочь новичкам освоить основы языка Swift через практику — читай код, играй, разбирайся!
- Как запустить
- Сюжет
- Уровни
- Структура проекта
- Swift: конструкции по файлам
- Карта обучения
- Частые вопросы
Убедись, что у тебя установлен Swift 5.9+:
swift --versionСборка и запуск:
cd SpaceQuest
swift runИли собери, а потом запусти бинарник:
swift build
.build/debug/SpaceQuestТы — капитан космического корабля «Комета». Корабль терпит крушение на неизвестной планете (это Земля, но ты ещё не знаешь 😉).
Двигатель повреждён. Чтобы улететь, нужно найти 5 деталей:
- ⚙️ Топливный насос
- 🌡️ Термостат
- 🧲 Катушка зажигания
- 🧠 Процессор
- 🔩 Уплотнитель
Каждая деталь — на отдельном уровне-испытании. Собери все 5 — почини двигатель и улетай!
| # | Уровень | Что нужно делать |
|---|---|---|
| 1 | Крушение | Выбраться из обломков. Выбрать направление |
| 2 | Лес | Сразиться с диким волком |
| 3 | Завод | Угадать код сейфа (подсказка: 42) |
| 4 | Страж | Битва с босс-роботом |
| 5 | Запуск | Разобрать завал, найти последнюю деталь |
SpaceQuest/
├── Package.swift # Конфигурация Swift Package Manager
├── README.md # Этот файл (документация)
└── Sources/SpaceQuest/
├── main.swift # Точка входа (минимальный код)
├── Game.swift # Игровой движок (class, Dictionary, циклы)
├── Player.swift # Модель игрока (struct, didSet, lazy)
├── Enemy.swift # Враги и бой (class, наследование, inout)
├── Levels.swift # 5 уровней (функции, switch, while)
└── Utils.swift # Шпаргалка по Swift (closures, async, enum, и т.д.)
Важно: Utils.swift не используется в игре. Это шпаргалка с примерами синтаксиса. Открывай её, когда хочешь вспомнить, как работает конкретная конструкция.
| Конструкция | Описание |
|---|---|
import PackageDescription |
Подключение модуля SPM |
let |
Константа (неизменяемое значение) |
Package(name:platforms:targets:) |
Структура с именованными параметрами |
[.macOS(.v13)] |
Массив с элементами-значениями |
| Конструкция | Описание |
|---|---|
import Foundation |
Подключение стандартной библиотеки Foundation |
let + Game() |
Создание константы через инициализатор |
game.start() |
Вызов метода через точку |
| Конструкция | Описание |
|---|---|
class Game |
class — ссылочный тип (хип, ARC, наследование) |
var player = Player(...) |
var — изменяемое свойство |
var foundParts: [String: Bool] |
Dictionary — словарь «ключ: значение» |
var allPartsFound: Bool { ... } |
Computed property — вычисляемое свойство |
foundParts.values.allSatisfy { } |
Метод с closure — allSatisfy принимает замыкание |
let levels: [(Game) -> Void] |
Массив функций — функции как значения |
levels[levelIndex](self) |
Вызов функции из массива |
while isRunning && ... |
while — цикл с условием |
if let name = readLine() |
if let — безопасное извлечение Optional |
"\(player.name)" |
Интерполяция строк — подстановка значений |
private func |
private — модификатор доступа (только внутри) |
player.heal(30) |
Вызов mutating метода у struct через var-свойство class |
Почему Game — class, а не struct? Игра — единственный «центр управления». Мы хотим, чтобы все ссылались на один и тот же объект, а не на копии.
| Конструкция | Описание |
|---|---|
struct Player |
struct — значениевый тип (стек, копируется) |
let id = UUID() |
let — константа (неизменяемая после создания) |
var name: String { didSet { } } |
Property observer — наблюдение за изменениями |
var health: Int { didSet { } } |
didSet для валидации (не даём уйти за 0 или max) |
var isAlive: Bool { health > 0 } |
Computed property — без хранения, только get |
lazy var greeting: String = { }() |
Lazy var — инициализация при первом доступе |
init(name:health:maxHealth:) |
init — инициализатор (конструктор) |
mutating func takeDamage() |
mutating func — метод, изменяющий struct |
Почему Player — struct, а не class? Игрок — это модель данных. struct копируется при присвоении, что безопаснее для конкурентности. Плюс стек быстрее хипа.
didSet vs willSet:
didSet— вызывается ПОСЛЕ изменения (естьoldValue)willSet— вызывается ДО изменения (естьnewValue)
| Конструкция | Описание |
|---|---|
class Enemy |
class — базовый класс |
init(name:health:attackPower:) |
Designated init — главный инициализатор |
convenience init(name: String) |
Convenience init — вспомогательный, вызывает self.init |
func attack() -> Int |
Обычный метод |
deinit { } |
deinit — вызывается при удалении объекта (ARC) |
class BossEnemy: Enemy |
Наследование — класс-потомок |
override func attack() |
override — переопределение метода родителя |
super.init(...) |
super — вызов родительского инициализатора |
func fight(player: inout Player) |
inout — передача параметра по ссылке |
switch choice { case 1: ... } |
switch — множественное ветвление с default |
guard let ... else { return } |
guard — ранний выход из функции |
continue |
continue — переход к следующей итерации цикла |
Про weak и ARC (подробнее в Utils.swift):
weak— слабая ссылка (не увеличивает счётчик ARC)unowned— как weak, но краш при обращении к удалённому объектуdeinit— подтверждает, что объект удалён из памяти
| Конструкция | Описание |
|---|---|
func levelCrash(game: Game) |
Функция верхнего уровня (не в классе) |
readLine() |
Чтение строки из терминала (возвращает String?) |
Int(input) |
Преобразование String в Int (возвращает Int?) |
switch choice { case 1: ... } |
switch с разными case + default |
while attempts > 0 { } |
while с счётчиком |
rubble.randomElement() |
randomElement() — случайный элемент массива |
rubble.firstIndex(of: "камень") |
firstIndex(of:) — поиск индекса |
rubble.remove(at: idx) |
remove(at:) — удаление по индексу |
Bool.random() |
Случайный true/false |
_ = readLine() |
_ (wildcard) — игнорируем返回值 |
return |
Выход из функции (с return и без) |
break |
Выход из цикла |
Шпаргалка с примерами. Содержит:
| Раздел | Конструкции |
|---|---|
| 1. Функции | Параметры, return, default, variadic, inout |
| 2. Closures | Анонимные функции, @escaping, trailing, capture |
| 3. Memory / ARC | class, weak, unowned, deinit |
| 4. async / await | async func, await, Task, Task.sleep |
| 5. Enum | enum, rawValue, associated values, computed property |
| 6. Switch | Диапазоны, where, exhaustiveness |
| 7. Optionals | ?, !, ??, if let, guard let |
| 8. Error handling | throws, do-catch, try?, try! |
| 9. Protocol + Extension | protocol, struct: Protocol, extension Int |
| 10. Generics | , where T: Equatable |
Если ты совсем новичок — изучай в таком порядке:
- Переменные:
varvslet(Game.swift:38-41) - Типы данных:
String,Int,Bool,[String](Player.swift:24-57) - Функции:
func name(param: Type) -> Return(Levels.swift:20) - Условные операторы:
if,guard,switch(Enemy.swift:120) - Циклы:
while,for-in(Levels.swift:104, 172) - Optional:
?,!,??,if let,guard let(Game.swift:114)
struct— значениевый тип (Player.swift:24)class— ссылочный тип (Enemy.swift:21)- Разница struct vs class (таблица в Game.swift:25-32)
- Инициализаторы:
init,convenience init(Enemy.swift:30-47) - Наследование:
class BossEnemy: Enemy(Enemy.swift:70) override— переопределение (Enemy.swift:86)
- Computed properties (Game.swift:55, Player.swift:57)
- Property observers:
didSet(Player.swift:35) mutating funcв struct (Player.swift:78)- Closures — замыкания (Utils.swift:69-114)
inoutпараметры (Enemy.swift:99)deinit— удаление из памяти (Enemy.swift:62)- Dictionary
[Key: Value](Game.swift:45) lazy var— ленивая инициализация (Player.swift:61)
- Error handling:
throws,do-catch(Utils.swift:278-314) - async/await:
Task, асинхронность (Utils.swift:163-179) - weak/unowned: ARC, память (Utils.swift:116-157)
- Protocol: контракты для типов (Utils.swift:321-336)
- Extension: расширение типов (Utils.swift:340-344)
- Generics
<T>: обобщённое программирование (Utils.swift:354-372)
Q: Игра не запускается, что делать?
A: Проверь Swift: swift --version (нужна версия 5.9+).
Если нет Swift — установи Xcode или brew install swift.
Q: Можно играть на Linux/Windows? A: Swift есть на Linux (без readLine могут быть нюансы). На Windows — через WSL или Swift For Windows.
Q: Почему комментарии на русском? A: Чтобы новичкам было понятнее. Программирование — сложная штука, незачем усложнять её английским на старте.
Q: Как добавить новый уровень?
A: 1. Напиши функцию в Levels.swift: func level6(game: Game)
2. Добавь её в массив levels в Game.swift
Q: Как изменить сложность?
A: Измени maxHealth игрока (Player.swift:69)
или health/attackPower врагов (Enemy.swift:30-47).
Учебный проект. Можно копировать, менять, распространять. Единственная цель — помочь людям выучить Swift. 🚀