230 lines
9.7 KiB
Swift
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),
|
|
]
|
|
}
|