Something went wrong. Try again.
Zork (Dungeon) built with Zig 0.17, wrapping the C port of the MIT mainframe game in one executable with its game data embedded.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288// SPDX-FileCopyrightText: 2026 Jeffrey C. Ollie <jeff@ocjtech.us>//// SPDX-License-Identifier: MIT
//! The game's initialization, in place of upstream's dinit.c.//!//! It exports the same two symbols that file did -- `init_`, which dmain.c//! calls once at startup, and `dbfile`, the stream dsub.c reads message text//! from -- so the rest of the C is untouched. What changes is where the game//! data comes from: rather than opening dtextc.dat from a path baked in at//! compile time, it is embedded in the executable, so the binary runs from//! anywhere and there is no data file to install or lose.
const std = @import("std");const build_options = @import("build_options");const c = @import("c");
/// The game database: an index of the initial world state, followed by the/// message text that dsub.c seeks into.const data = @embedFile("dtextc.dat");
/// The game database as a stream. dsub.c reads message text from it with/// `fseek`, `getc` and `ftell`, and closes it on exit, so it has to be a real/// `FILE`; `fmemopen` makes one over the embedded bytes.export var dbfile: ?*c.FILE = null;
/// Set up the initial game state from the embedded database. Returns true on/// success; on failure it has already told the player why.export fn init_() c.logical { c.more_init();
if (c.protected() == 0) { for ([_][*:0]const u8{ "There appears before you a threatening figure clad all over", "in heavy black armor. His legs seem like the massive trunk", "of the oak tree. His broad shoulders and helmeted head loom", "high over your own puny frame, and you realize that his powerful", "arms could easily crush the very life from your body. There", "hangs from his belt a veritable arsenal of deadly weapons:", "sword, mace, ball and chain, dagger, lance, and trident.", "He speaks with a commanding voice:", "", " \"You shall not pass.\"", "", "As he grabs you by the neck all grows dim about you.", }) |line| c.more_output(line); c.exit_(); }
resetState();
var index: Index = .{ .bytes = data }; const vmaj = index.int(); const vmin = index.int(); const vedit = index.int(); if (vmaj != c.vers_.vmaj or vmin != c.vers_.vmin) { var buffer: [64]u8 = undefined; c.more_output(std.mem.printSentinel(&buffer, "The embedded game data is version {d}.{d}{c}.", .{ vmaj, vmin, @as(u8, @truncate(@as(c_uint, @bitCast(vedit)))), }, 0) catch unreachable); c.more_output(std.mem.printSentinel(&buffer, "I require version {d}.{d}{c}.", .{ c.vers_.vmaj, c.vers_.vmin, @as(u8, @truncate(@as(c_uint, @bitCast(c.vers_.vedit)))), }, 0) catch unreachable); return failed(); } index.load();
dbfile = c.fmemopen(@constCast(data.ptr), data.len, "rb") orelse { c.more_output("I can't open the embedded game data."); return failed(); };
c.itime_(&c.time_.shour, &c.time_.smin, &c.time_.ssec);
c.play_.winner = c.aindex_.player; c.last_.lastit = c.advs_.aobj[@intCast(c.aindex_.player - 1)]; c.play_.here = c.advs_.aroom[@intCast(c.play_.winner - 1)]; c.hack_.thfpos = c.objcts_.oroom[@intCast(c.oindex_.thief - 1)]; c.state_.bloc = c.objcts_.oroom[@intCast(c.oindex_.ballo - 1)];
return c.TRUE_;}
/// Everything init_ sets before reading the database, in the order dinit.c/// set it.fn resetState() void { zero(&c.rmsg_); zero(&c.objcts_); zero(&c.rooms_); zero(&c.vill_); zero(&c.advs_); zero(&c.cevent_); zero(&c.exits_); zero(&c.oroom2_); c.exits_.xlnt = 1;
c.state_.ltshft = 10; c.state_.mxscor = c.state_.ltshft; c.state_.egscor = 0; c.state_.egmxsc = 0; c.state_.mxload = 100; c.state_.rwscor = 0; c.state_.deaths = 0; c.state_.moves = 0; c.time_.pltime = 0; c.state_.mungrm = 0; c.state_.hs = 0;
// The parse vector and the orphans. c.prsvec_.prsa = 0; c.prsvec_.prsi = 0; c.prsvec_.prso = 0; c.prsvec_.prscon = 1; zero(&c.orphs_);
// The thief is not yet introduced but is active; the sword is inactive // and off. c.hack_.thfflg = c.FALSE_; c.hack_.thfact = c.TRUE_; c.hack_.swdact = c.FALSE_; c.hack_.swdsta = 0;
c.star_.mbase = 0;
// The flags and switches, of which only these start out set. zero(&c.findex_); c.findex_.buoyf = c.TRUE_; c.findex_.egyptf = c.TRUE_; c.findex_.cagetf = c.TRUE_; c.findex_.mr1f = c.TRUE_; c.findex_.mr2f = c.TRUE_; c.findex_.follwf = c.TRUE_; c.findex_.ormtch = 4; c.findex_.lcell = 1; c.findex_.pnumb = 1; c.findex_.mdir = 270; c.findex_.mloc = c.rindex_.mrb; c.findex_.cphere = 10;
zero(&c.debug_); // The game debugging tool is available to the wizard, without having to // recompile, when it has been built in. if (build_options.allow_gdt and c.wizard() != 0) c.debug_.gdtflg = 1;
// Not the whole of screen_, which also holds the Scol rooms' direction // tables. c.screen_.fromdr = 0; c.screen_.scolrm = 0; c.screen_.scolac = 0;}
fn zero(ptr: anytype) void { ptr.* = std.mem.zeroes(@TypeOf(ptr.*));}
fn failed() c.logical { for ([_][*:0]const u8{ "Suddenly a sinister, wraithlike figure appears before you,", "seeming to float in the air. In a low, sorrowful voice he says,", "\"Alas, the very nature of the world has changed, and the dungeon", "cannot be found. All must now pass away.\" Raising his oaken staff", "in farewell, he fades into the spreading darkness. In his place", "appears a tastefully lettered sign reading:", "", " INITIALIZATION FAILURE", "", "The darkness becomes all encompassing, and your vision fails.", }) |line| c.more_output(line); return c.FALSE_;}
/// A reader over the index at the front of the database, in the encoding/// dinit.c read with `getc`: integers are 16-bit big-endian two's/// complement, flags a byte each.const Index = struct { bytes: []const u8, pos: usize = 0,
fn byte(index: *Index) u8 { defer index.pos += 1; return index.bytes[index.pos]; }
fn int(index: *Index) c.integer { defer index.pos += 2; return std.mem.readInt(i16, index.bytes[index.pos..][0..2], .big); }
/// A count, followed by nothing: the arrays it sizes come after. fn count(index: *Index, n: *c.integer) usize { n.* = index.int(); return @intCast(n.*); }
fn ints(index: *Index, dest: []c.integer) void { for (dest) |*d| d.* = index.int(); }
/// A sparse array, as (position, value) pairs ending in a sentinel. The /// positions are a byte, ending in 255, for an array shorter than 255, /// and an integer, ending in -1, otherwise. fn partialInts(index: *Index, len: usize, dest: []c.integer) void { while (true) { const i: usize = if (len < 255) i: { const i = index.byte(); if (i == 255) return; break :i i; } else i: { const i = index.int(); if (i == -1) return; break :i @intCast(i); }; dest[i] = index.int(); } }
fn flags(index: *Index, dest: []c.logical) void { for (dest) |*d| d.* = index.byte(); }
/// Everything after the version, into the game's global state. fn load(index: *Index) void { c.state_.mxscor = index.int(); c.star_.strbit = index.int(); c.state_.egmxsc = index.int();
const r = index.count(&c.rooms_.rlnt); index.ints(c.rooms_.rdesc1[0..r]); index.ints(c.rooms_.rdesc2[0..r]); index.ints(c.rooms_.rexit[0..r]); index.partialInts(r, &c.rooms_.ractio); index.partialInts(r, &c.rooms_.rval); index.ints(c.rooms_.rflag[0..r]);
const x = index.count(&c.exits_.xlnt); index.ints(c.exits_.travel[0..x]);
const o = index.count(&c.objcts_.olnt); index.ints(c.objcts_.odesc1[0..o]); index.ints(c.objcts_.odesc2[0..o]); index.partialInts(o, &c.objcts_.odesco); index.partialInts(o, &c.objcts_.oactio); index.ints(c.objcts_.oflag1[0..o]); index.partialInts(o, &c.objcts_.oflag2); index.partialInts(o, &c.objcts_.ofval); index.partialInts(o, &c.objcts_.otval); index.ints(c.objcts_.osize[0..o]); index.partialInts(o, &c.objcts_.ocapac); index.ints(c.objcts_.oroom[0..o]); index.partialInts(o, &c.objcts_.oadv); index.partialInts(o, &c.objcts_.ocan); index.partialInts(o, &c.objcts_.oread);
const r2 = index.count(&c.oroom2_.r2lnt); index.ints(c.oroom2_.oroom2[0..r2]); index.ints(c.oroom2_.rroom2[0..r2]);
const ce = index.count(&c.cevent_.clnt); index.ints(c.cevent_.ctick[0..ce]); index.ints(c.cevent_.cactio[0..ce]); index.flags(c.cevent_.cflag[0..ce]);
const v = index.count(&c.vill_.vlnt); index.ints(c.vill_.villns[0..v]); index.partialInts(v, &c.vill_.vprob); index.partialInts(v, &c.vill_.vopps); index.ints(c.vill_.vbest[0..v]); index.ints(c.vill_.vmelee[0..v]);
const a = index.count(&c.advs_.alnt); index.ints(c.advs_.aroom[0..a]); index.partialInts(a, &c.advs_.ascore); index.partialInts(a, &c.advs_.avehic); index.ints(c.advs_.aobj[0..a]); index.ints(c.advs_.aactio[0..a]); index.ints(c.advs_.astren[0..a]); index.partialInts(a, &c.advs_.aflag);
c.star_.mbase = index.int(); const m = index.count(&c.rmsg_.mlnt); index.ints(c.rmsg_.rtext[0..m]);
// Where the message text starts, which dsub.c adds to every offset // in rtext. c.rmsg_.mrloc = @intCast(index.pos); }};