Files
Arcade85/Arcade85/Games/Galaxian/GalaxianData.swift
T

230 lines
9.7 KiB
Swift

import Foundation
/// Static data for Galaxian, transcribed verbatim from `GALAXIAN.ASM`
/// (Patrick Davidson, v3.0, 1998): every sprite bitmap, the six formation
/// patterns, and the 20-level campaign table.
enum GalaxianData {
// MARK: - Sprites
/// A 1-bit sprite. `rows[i]` holds row `i` with bit 31 as the leftmost
/// pixel (the ASM's row bytes packed big-endian, as drawn by `drw_spr`).
struct Sprite {
let w: Int
let h: Int
let rows: [UInt32]
init(_ w: Int, _ h: Int, _ byteRows: [[UInt8]]) {
self.w = w
self.h = h
self.rows = byteRows.map { bytes in
var v: UInt32 = 0
for (i, b) in bytes.enumerated() { v |= UInt32(b) << (24 - 8 * i) }
return v
}
}
}
enum Img: Hashable {
case enemy1, enemy2, enemy3, enemy4, cross, operatorShip
case boss1, boss2, boss3
case ship, playerBullet, enemyBullet
case x1, x2, x3, x4, x5, x6, x7, x8 // explosion frames
}
static let explosionFrames: [Img] = [.x1, .x2, .x3, .x4, .x5, .x6, .x7, .x8]
static let sprites: [Img: Sprite] = [
// enemy_warped (the "cross" trainer)
.cross: Sprite(7, 7, [
[0b1100_0110], [0b1110_1110], [0b0111_1100], [0b0011_1000],
[0b0111_1100], [0b1110_1110], [0b1100_0110],
]),
.enemy1: Sprite(6, 6, [
[0b1000_0100], [0b1000_0100], [0b1111_1100], [0b1111_1100],
[0b0111_1000], [0b0011_0000],
]),
.enemy2: Sprite(6, 7, [
[0b1000_0100], [0b1100_1100], [0b1111_1100], [0b1000_0100],
[0b1000_0100], [0b0100_1000], [0b0011_0000],
]),
.enemy3: Sprite(6, 5, [
[0b1000_0100], [0b1111_1100], [0b1000_0100], [0b0100_1000],
[0b0011_0000],
]),
.enemy4: Sprite(7, 5, [
[0b1000_0010], [0b1100_0110], [0b1010_1010], [0b1001_0010],
[0b1000_0010],
]),
.operatorShip: Sprite(7, 7, [
[0b0111_1100], [0b1111_1110], [0b1100_0110], [0b1100_0110],
[0b1100_0110], [0b1111_1110], [0b0111_1100],
]),
.boss1: Sprite(16, 8, [
[0b1000_0000, 0b0000_0001], [0b1100_0000, 0b0000_0011],
[0b1011_1111, 0b1111_1101], [0b1000_0001, 0b1000_0001],
[0b1000_0001, 0b1000_0001], [0b0100_0001, 0b1000_0010],
[0b0010_0001, 0b1000_0100], [0b0001_1111, 0b1111_1000],
]),
.boss2: Sprite(16, 8, [
[0b1111_1111, 0b1111_1111], [0b1110_0000, 0b0000_0111],
[0b1110_0000, 0b0000_0111], [0b1110_0000, 0b0000_0111],
[0b1110_0000, 0b0000_0111], [0b1111_0000, 0b0000_1111],
[0b1110_1000, 0b0001_0111], [0b0100_0111, 0b1110_0010],
]),
.boss3: Sprite(31, 8, [
[0b1111_1111, 0b1111_1111, 0b1111_1111, 0b1111_1110],
[0b1010_1000, 0b0000_0000, 0b0000_0000, 0b0010_1010],
[0b1010_1001, 0b0101_0101, 0b0101_0101, 0b0010_1010],
[0b1010_1000, 0b0000_0000, 0b0000_0000, 0b0010_1010],
[0b1010_1000, 0b0000_0111, 0b1100_0000, 0b0010_1010],
[0b1000_1111, 0b1100_0010, 0b1000_0111, 0b1110_0010],
[0b0101_0000, 0b0010_0001, 0b0000_1000, 0b0001_0100],
[0b0010_0000, 0b0001_1111, 0b1111_0000, 0b0000_1000],
]),
.enemyBullet: Sprite(3, 3, [
[0b0100_0000], [0b1110_0000], [0b0100_0000],
]),
.playerBullet: Sprite(3, 5, [
[0b0100_0000], [0b1110_0000], [0b1110_0000], [0b1110_0000],
[0b1110_0000],
]),
.ship: Sprite(7, 7, [
[0b0001_0000], [0b0011_1000], [0b1011_1010], [0b1011_1010],
[0b1011_1010], [0b1111_1110], [0b1111_1110],
]),
.x1: Sprite(8, 6, [
[0b0000_0000], [0b0001_1100], [0b0011_1110], [0b0101_0110],
[0b0011_1000], [0b0000_0000],
]),
.x2: Sprite(8, 6, [
[0b0011_0000], [0b0100_1110], [0b1011_1110], [0b0100_1111],
[0b0011_1000], [0b0001_1010],
]),
.x3: Sprite(8, 6, [
[0b1111_0011], [0b0100_1110], [0b1011_0101], [0b0100_0101],
[0b0011_1110], [0b1101_1010],
]),
.x4: Sprite(8, 6, [
[0b1111_0011], [0b0100_1110], [0b1011_0101], [0b0100_0101],
[0b0011_1110], [0b1101_1010],
]),
.x5: Sprite(8, 6, [
[0b0100_0001], [0b0010_0110], [0b0001_0101], [0b0100_0100],
[0b0001_0010], [0b1001_1010],
]),
.x6: Sprite(8, 6, [
[0b0100_0010], [0b0010_0000], [0b0000_0001], [0b0100_0100],
[0b0010_0010], [0b1000_1010],
]),
.x7: Sprite(8, 6, [
[0b0000_1000], [0b1100_0010], [0b0000_0000], [0b0010_0000],
[0b0000_0001], [0b0011_0000],
]),
.x8: Sprite(8, 6, [
[0b0000_0100], [0b0000_0000], [0b0100_0000], [0b0000_0000],
[0b0000_0001], [0b0010_0100],
]),
]
// MARK: - Formation patterns
/// 20 (x, depth) pairs per pattern. The depth byte does double duty in the
/// original: an enemy triggers its entry when `timer & 127 == 128 - depth`
/// and descends 1 px/frame until the timer wraps — arriving at row `depth`
/// exactly on the wrap.
typealias Pattern = [(x: Int, y: Int)]
static let pattern1: Pattern = [
(10, 16), (20, 16), (30, 16), (40, 16), (50, 16),
(5, 24), (15, 24), (25, 24), (35, 24), (45, 24),
(50, 32), (10, 32), (20, 32), (30, 32), (40, 32),
(5, 40), (15, 40), (25, 40), (35, 40), (45, 40),
]
static let pattern2: Pattern = [
(1, 16), (14, 16), (27, 16), (40, 16), (53, 16),
(1, 24), (14, 24), (27, 24), (40, 24), (53, 24),
(1, 32), (14, 32), (27, 32), (40, 32), (53, 32),
(1, 40), (14, 40), (27, 40), (40, 40), (53, 40),
]
/// Shared by levels 3/7/11/16 (the boss levels): formation sits lower.
static let patternBoss: Pattern = [
(1, 26), (14, 26), (27, 26), (40, 26), (53, 26),
(1, 34), (14, 34), (27, 34), (40, 34), (53, 34),
(1, 42), (14, 42), (27, 42), (40, 42), (53, 42),
(1, 50), (14, 50), (27, 50), (40, 50), (53, 50),
]
/// Shared by the mixed-type levels 4/8/12 (7 + 6 + 7 layout).
static let patternWeak: Pattern = [
(5, 18), (13, 18), (21, 18), (29, 18), (37, 18), (45, 18), (53, 18),
(9, 28), (17, 28), (25, 28), (33, 28), (41, 28), (49, 28),
(5, 38), (13, 38), (21, 38), (29, 38), (37, 38), (45, 38), (53, 38),
]
/// Level 6's ring of enemies.
static let patternRing: Pattern = [
(53, 28), (52, 32), (48, 35), (42, 38), (35, 39), (27, 40), (19, 39),
(12, 38), (6, 35), (2, 32), (1, 28), (2, 24), (6, 21), (12, 18),
(19, 17), (27, 16), (35, 17), (42, 18), (48, 21), (52, 24),
]
/// Levels 10/14: four columns of five.
static let patternColumns: Pattern = [
(1, 16), (1, 22), (1, 28), (1, 34), (1, 40),
(19, 16), (19, 22), (19, 28), (19, 34), (19, 40),
(37, 16), (37, 22), (37, 28), (37, 34), (37, 40),
(55, 16), (55, 22), (55, 28), (55, 34), (55, 40),
]
// MARK: - Levels
enum LevelKind {
/// 20 formation enemies of one type.
case standard
/// 20 formation enemies + a patrolling boss (21 to clear).
case boss(img: Img, pwr: Int)
/// 13 weak (power-1, enemy-1 art, slots 0…12) + 7 of the level type.
case weak
/// 10 bouncing cross trainers (half of them can flee).
case cross
/// 10 bouncing operator ships that fire aimed shots.
case operators
/// Sweeping superboss (98 hits) + 8 formation enemies + drone deploys.
case superBoss
}
struct Level {
let kind: LevelKind
let enemyImg: Img
let enemyPwr: Int
let pattern: Pattern
}
/// levels[0] is level 1. Mirrors `levels_jump_table` exactly.
static let levels: [Level] = [
Level(kind: .standard, enemyImg: .enemy1, enemyPwr: 1, pattern: pattern1),
Level(kind: .standard, enemyImg: .enemy1, enemyPwr: 1, pattern: pattern2),
Level(kind: .boss(img: .boss1, pwr: 10), enemyImg: .enemy1, enemyPwr: 1, pattern: patternBoss),
Level(kind: .weak, enemyImg: .enemy2, enemyPwr: 2, pattern: patternWeak),
Level(kind: .cross, enemyImg: .cross, enemyPwr: 5, pattern: pattern1),
Level(kind: .standard, enemyImg: .enemy2, enemyPwr: 2, pattern: patternRing),
Level(kind: .boss(img: .boss2, pwr: 10), enemyImg: .enemy2, enemyPwr: 2, pattern: patternBoss),
Level(kind: .weak, enemyImg: .enemy3, enemyPwr: 3, pattern: patternWeak),
Level(kind: .cross, enemyImg: .cross, enemyPwr: 5, pattern: pattern1),
Level(kind: .standard, enemyImg: .enemy3, enemyPwr: 3, pattern: patternColumns),
Level(kind: .boss(img: .boss2, pwr: 10), enemyImg: .enemy3, enemyPwr: 3, pattern: patternBoss),
Level(kind: .weak, enemyImg: .enemy4, enemyPwr: 4, pattern: patternWeak),
Level(kind: .cross, enemyImg: .cross, enemyPwr: 5, pattern: pattern1),
Level(kind: .standard, enemyImg: .enemy4, enemyPwr: 4, pattern: patternColumns),
Level(kind: .operators, enemyImg: .operatorShip, enemyPwr: 8, pattern: pattern1),
Level(kind: .boss(img: .boss2, pwr: 10), enemyImg: .enemy4, enemyPwr: 4, pattern: patternBoss),
Level(kind: .operators, enemyImg: .operatorShip, enemyPwr: 8, pattern: pattern1),
Level(kind: .superBoss, enemyImg: .enemy1, enemyPwr: 1, pattern: pattern1),
Level(kind: .operators, enemyImg: .operatorShip, enemyPwr: 8, pattern: pattern1),
Level(kind: .superBoss, enemyImg: .enemy4, enemyPwr: 4, pattern: pattern1),
]
}