diff --git a/games/punderplay-zig/.gitignore b/games/punderplay-zig/.gitignore new file mode 100644 index 0000000..58b12f2 --- /dev/null +++ b/games/punderplay-zig/.gitignore @@ -0,0 +1,3 @@ +zig-cache/ +zig-out/ +.zigmod diff --git a/games/punderplay-zig/README.md b/games/punderplay-zig/README.md new file mode 100644 index 0000000..4ba78b5 --- /dev/null +++ b/games/punderplay-zig/README.md @@ -0,0 +1,27 @@ +# Punderplay + +### build + +Install [`zigmod`](https://github.com/nektro/zigmod#download) and run: + +```sh +# from the `punderplay` directory +zigmod fetch +zig build +# use ./zig-out/bin/punderplay.wasm +``` + +### Testing + +Run unit tests via: +```sh +zig build test-game +zig build test-store +``` + +Execute functions in the game: + +```sh +# currently this just echoes out the input after deserializing & reserializing +extism call zig-out/bin/punderplay.wasm init_game --input '{"player_ids": ["steve", "ben"]}' | jq . +``` \ No newline at end of file diff --git a/games/punderplay-zig/build.zig b/games/punderplay-zig/build.zig new file mode 100644 index 0000000..f1eb833 --- /dev/null +++ b/games/punderplay-zig/build.zig @@ -0,0 +1,27 @@ +const std = @import("std"); +const deps = @import("deps.zig"); + +pub fn build(b: *std.build.Builder) void { + const mode = b.standardReleaseOptions(); + // const target = b.standardTargetOptions(.{ .default_target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding } }); + const target = b.standardTargetOptions(.{ + .default_target = .{ .abi = .musl, .os_tag = .freestanding, .cpu_arch = .wasm32 }, + }); + const exe = b.addExecutable("punderplay", "src/main.zig"); + exe.setTarget(target); + exe.setBuildMode(mode); + deps.addAllTo(exe); + exe.install(); + + const game_tests = b.addTest("src/game.zig"); + deps.addAllTo(game_tests); + + const store_tests = b.addTest("src/store.zig"); + deps.addAllTo(store_tests); + + const game_test_step = b.step("test-game", "run `game` tests"); + game_test_step.dependOn(&game_tests.step); + + const store_test_step = b.step("test-store", "run `store` tests"); + store_test_step.dependOn(&store_tests.step); +} diff --git a/games/punderplay-zig/deps.zig b/games/punderplay-zig/deps.zig new file mode 100644 index 0000000..3a31f70 --- /dev/null +++ b/games/punderplay-zig/deps.zig @@ -0,0 +1,86 @@ +// zig fmt: off +const std = @import("std"); +const builtin = @import("builtin"); +const Pkg = std.build.Pkg; +const string = []const u8; + +pub const cache = ".zigmod/deps"; + +pub fn addAllTo(exe: *std.build.LibExeObjStep) void { + checkMinZig(builtin.zig_version, exe); + @setEvalBranchQuota(1_000_000); + for (packages) |pkg| { + exe.addPackage(pkg.pkg.?); + } + var llc = false; + var vcpkg = false; + inline for (comptime std.meta.declarations(package_data)) |decl| { + const pkg = @as(Package, @field(package_data, decl.name)); + for (pkg.system_libs) |item| { + exe.linkSystemLibrary(item); + llc = true; + } + for (pkg.frameworks) |item| { + if (!std.Target.current.isDarwin()) @panic(exe.builder.fmt("a dependency is attempting to link to the framework {s}, which is only possible under Darwin", .{item})); + exe.linkFramework(item); + llc = true; + } + inline for (pkg.c_include_dirs) |item| { + exe.addIncludePath(@field(dirs, decl.name) ++ "/" ++ item); + llc = true; + } + inline for (pkg.c_source_files) |item| { + exe.addCSourceFile(@field(dirs, decl.name) ++ "/" ++ item, pkg.c_source_flags); + llc = true; + } + vcpkg = vcpkg or pkg.vcpkg; + } + if (llc) exe.linkLibC(); + if (builtin.os.tag == .windows and vcpkg) exe.addVcpkgPaths(.static) catch |err| @panic(@errorName(err)); +} + +pub const Package = struct { + directory: string, + pkg: ?Pkg = null, + c_include_dirs: []const string = &.{}, + c_source_files: []const string = &.{}, + c_source_flags: []const string = &.{}, + system_libs: []const string = &.{}, + frameworks: []const string = &.{}, + vcpkg: bool = false, +}; + +fn checkMinZig(current: std.SemanticVersion, exe: *std.build.LibExeObjStep) void { + const min = std.SemanticVersion.parse("null") catch return; + if (current.order(min).compare(.lt)) @panic(exe.builder.fmt("Your Zig version v{} does not meet the minimum build requirement of v{}", .{current, min})); +} + +pub const dirs = struct { + pub const _root = ""; + pub const _f0fex8nt61gp = cache ++ "/../.."; + pub const _hybfpvr9c5u2 = cache ++ "/git/github.com/extism/zig-pdk"; +}; + +pub const package_data = struct { + pub const _f0fex8nt61gp = Package{ + .directory = dirs._f0fex8nt61gp, + }; + pub const _hybfpvr9c5u2 = Package{ + .directory = dirs._hybfpvr9c5u2, + .pkg = Pkg{ .name = "extism-pdk", .source = .{ .path = dirs._hybfpvr9c5u2 ++ "/src/main.zig" }, .dependencies = null }, + }; + pub const _root = Package{ + .directory = dirs._root, + }; +}; + +pub const packages = &[_]Package{ + package_data._hybfpvr9c5u2, +}; + +pub const pkgs = struct { + pub const extism_pdk = package_data._hybfpvr9c5u2; +}; + +pub const imports = struct { +}; diff --git a/games/punderplay-zig/src/game.zig b/games/punderplay-zig/src/game.zig new file mode 100644 index 0000000..5870ca6 --- /dev/null +++ b/games/punderplay-zig/src/game.zig @@ -0,0 +1,112 @@ +const std = @import("std"); +const Plugin = @import("extism-pdk").Plugin; +const store = @import("store.zig"); + +pub const Player = []const u8; + +pub const Judge = Player; + +pub const SubmittedPun = struct { + prompt: [2][]const u8, + player: Player, +}; + +pub const Round = struct { + winner: ?Player, + winning_pun: ?[]const u8, + prompt: ?[2][]const u8, + submitted_puns: ?[]SubmittedPun, +}; + +pub const Config = struct { + player_ids: []Player, +}; + +pub const PlayerScore = struct { + player: Player, + score: u8, +}; + +pub const Game = struct { + current_judge: Judge, + players: []PlayerScore, + rounds: []Round, + version: u32, + + const Self = @This(); + + pub fn init(self: *Game, allocator: std.mem.Allocator, players: []Player) !void { + defer allocator.free(players); + // pick a player from the set of current players to act as the judge. + // initialize the scores for each player. + self.players = try allocator.alloc(PlayerScore, players.len); + for (players) |player, i| { + self.players[i] = PlayerScore{ .player = player, .score = 0 }; + } + + self.current_judge = players[0]; + self.rounds = &[_]Round{}; + self.version = 0; + } + + pub fn getCurrentRound(self: *Self) Round { + return self.rounds[self.rounds.len - 1]; + } + + pub fn updateCurrentRound(self: *Self, round: Round) void { + self.rounds[self.rounds.len - 1] = round; + } + + // pub fn deinit(self: *Game, allocator: std.mem.Allocator) void { + // allocator.free(self.players); + // } + + pub fn toJson(self: Self, allocator: std.mem.Allocator) ![]const u8 { + return std.json.stringifyAlloc(allocator, self, .{}); + } + + pub fn fromJson(self: *Game, allocator: std.mem.Allocator, data: []const u8) !void { + var stream = std.json.TokenStream.init(data); + const game = try std.json.parse(Game, &stream, .{ .allocator = allocator }); + defer std.json.parseFree(Game, game, .{ .allocator = allocator }); + + self.current_judge = game.current_judge; + self.players = game.players; + self.rounds = game.rounds; + self.version = game.version; + } + + pub fn inc_version(self: *Self) void { + self.version += 1; + } +}; + +const verbs = "watering.cataloging.hunting.wanting.holding.taping.integrating.worrying.loving.spending.fitting.bating.risking.normalizing.restructuring.costing.programming.touching.towing.altering.marketing.yelling.crushing.beholding.agreeing.fencing.sparkling.wiping.sparking.slaying.copying.melting.appraising.complaining.leading.telling.crashing.subtracting.normalizing.grabbing.wrecking.thanking.forming.answering.overhearing.wriggling.rin.ing.admitting.bruising.making.pumping.melting.bumping.dragging.consisting.accepting.dropping.smelling.recognizing.facing.deciding.deserting.riding.ensuring.frightening.shading.flapping.washing.completing.heaping.snoring.draining.clothing.detailing.initiating.dispensing.diagnosing.paddling.sin.ing.promising.handling.planing.separating.thriving.shrinking.scrubbing.confusing.spotting.scattering.noticing.upgrading.piloting.estimating.showing.reigning.folding.contracting.blushing.broadcasting.speaking.slipping.squashing.pecking.hanging.returning.receiving.landing.injecting.fleeing.cheering.sniffing.sleeping.clin.ing.breeding.searching.carving.meaning.attaching.affording.nesting.undergoing.passing.entertaining.longing.enjoying.fighting.wrestling.unfastening.drawing.supposing.knotting.greasing.producing.spinning.squashing.asking.projecting.enduring.adopting.fancying.conducting.conceiving.guessing.mating.overthrowing.regulating.determining.bearing.devising.abiding.piloting.hearing.rhyming.retrieving.servicing.integrating.preaching.rubbing.clarifying.agreeing.striking.wobbling.groaning.speeding.filling.repairing.pining.launching.sneaking.shaping.breathing.spoiling.living.recruiting.proposing.pedaling.wrecking.replacing.operating.trying.licensing.discovering.overdoing.rinsing.camping.displaying.muddling.pricking.nesting.processing.counseling.consolidating.shivering.numbering.removing.sliding.referring.rin.ing.representing.risking.inspecting.assisting.enhancing.administering.identifying.enacting.skipping.shaking.spoiling.spelling.selecting.shopping.causing.reflecting.photographing.withstanding.evaluating.breaking.visiting.creeping.feeding.loading.graduating.combing.tickling.catching.dividing.squealing.breathing.fixing.floating.logging.chewing.carrying.hurting.sacking.expressing.getting.forbidding.sawing.moaning.grinding.ruining.hurrying.balancing.exploding.spilling.welcoming.eliminating.acceding.classifying.smiling.assuring.settling.scheduling.perceiving.moaning.shooting.reconciling.faxing.executing.decaying.marrying.stin.ing.investigating.enacting.caring.questioning.proving.rescuing.filming.shopping.separating.identifying.leading.laying.speeding.tracing.identifying.alerting.sacking.remaining.activating.interesting.boasting.imagining.putting.controlling.disliking.addressing.solving.fleeing.agreeing.shining.fancying.wrin.ing.fading.accelerating.establishing.curling.attacking.guaranteeing.deceiving.patting.applauding.noting.pressing.kneeling.hitting.scheduling.presiding.repeating.prescribing.arising.slaying.adding.fitting.snoring.shaking.sewing.inspecting.educating.manipulating.belonging.giving.participating.agreeing.doubting.misunderstanding.following.trotting.writing.clin.ing.interesting.damming.correlating.plugging.attending.retiring.beholding.understanding.walking.pinpointing.photographing.braking.soaking.folding.remembering.slin.ing.borrowing.rocking.allowing.filming.obeying.coiling.cycling.untidying.shrinking.preferring.stirring.fixing.attending.baking.saying.rotting.learning.governing.confessing.reminding.utilizing.bruising.dramatizing.knowing.puncturing.shearing.suggesting.achieving.heading.sliding.punching.greeting.gazing.swin.ing.spending.occurring.sneezing.creating.sparking.hiding.stitching.promoting.changing.snowing.taking.lying.reading.responding.bouncing.baking.pecking.multiplying.conserving.challenging.losing.sowing.fooling.marketing.informing.spilling.matching.speaking.dealing.detecting.rating.screwing.bruising.interlaying.phoning.ensuring.binding.stoping.scattering.weighing.participating.editing.letting.publicizing.preparing.financing.shearing.checking.writing.exciting.firing.bombing.disliking.restoring.eating.fighting.answering.balancing.sending.blotting.amusing.disagreeing.innovating.relying.correcting.confronting.judging.reducing.entertaining.arguing.selecting.stamping.parking.naming.noticing.doing.chopping.banging.engineering.trusting.describing.delegating.cheating.lending.mistaking.squeaking.winding.comparing.adapting.cracking.correlating.scolding.blushing.scraping.buzzing.existing.improvising.praising.splitting.distributing.curing.overdoing.mapping.progressing.activating.feeding.forgiving.dressing.manning.polishing.smoking.reconciling.knowing.tugging.being.causing.shutting.preventing.disappearing.presenting.banging.explaining.crossing.predicting.converting.sighing.designing.fooling.bursting.decorating.battling.hearing.sensing.meaning.presetting.chasing.overdrawing.classifying.charging.parting.polishing.planting.sniffing.fearing.commanding.formulating.bruising.distributing.officiating.seeking.offending.harming.computing.realigning.touring.containing.revising.surprising.helping.pleading.overflowing.reinforcing.nodding.matching.barring.ascertaining.budgeting.allowing.lightening.presetting.deceiving.collecting.slitting.becoming.collecting.cleaning.extending.boiling.overthrowing.obeying.working.painting.dealing.protecting.mugging.flin.ing.updating.referring.skiing.discovering.blinking.spelling.calling.curling.dealing.quitting.shaking.meaning.diverting.smiling.facilitating.satisfying.rolling.siting.asking.holding.blowing.kneeling.assuring.defining.disproving.recruiting.diagnosing.whispering.coughing.lasting.racing.sprouting.acquiring.stitching.heading.soothsaying.parking.splitting.programming.starting.including.knitting.making.jumping.staining.sensing.nominating.regulating.receiving.lasting.boxing.preceding.inventing.increasing.implementing.radiating.relaxing.guarding.brushing.heaping.killing.objecting.confronting.keeping.vexing.accomplishing.orienteering.sinking.repairing.instituting.coughing.preaching.juggling.trading.frying.lightening.scaring.hypothesizing.insuring.sealing.shoeing.grinning.ranking.teaching.coaching.creeping.rising.rubbing.bowing.systemizing.jamming.performing.framing.delaying.quitting.experimenting.smashing.leveling.skipping.sealing.breeding.blinking.beginning.owing.clearing.breaking.growing.guaranteeing.numbering.formulating.standing.verbalizing.executing.shaking.foreseeing.delighting.sneaking.hooking.pasting.objecting.coloring.setting.instructing.generating.measuring.glueing.reconciling.challenging.avoiding.missing.attaining.forecasting.tipping.sharing.bleaching.addressing.proofreading.intending.parting.queueing.crushing.sucking.monitoring.overcoming.copying.restructuring.chewing.casting.tying.ignoring.painting.examining.doubling.doubting.wishing.helping.spiting.wending.thrusting.splitting.muddling.confusing.liking.recognizing.designing.sending.reconciling.inducing.keeping.unifying.excusing.buzzing.joking.forgiving.conserving.budgeting.scratching.initiating.bumping.stamping.suspending.leveling.liking.scribbling.stinking.exhibiting.structuring.rating.queueing.belonging.fearing.broadcasting.hurrying.packing.brushing.glueing.tricking.wandering.bathing.inventorying.expanding.launching.reinforcing.soothing.communicating.predicting.simplifying.pressing.living.exceeding.spotting.disapproving.calculating.spotting.strapping.correcting.rotting.owing.bursting.encouraging.unpacking.cutting.originating.sketching.consisting.causing.affording.impressing.producing.hammering.sensing.scratching.borrowing.transforming.modifying.killing.besetting.lightening.spreading.mediating.sketching.wobbling.staring.adopting.strapping.laying.buying.biding.boasting.possessing.solving.expecting.covering.matching.accepting.rejecting.drying.mattering.blessing.cheering.hugging.wrapping.curing.attempting.meeting.sprin.ing.dressing.using.alighting.speeding.conceiving.meddling.losing.stimulating.clearing.forgetting.rocking.appreciating.rejoicing.increasing.rising.eating.critiquing.depending.spraying.keeping.researching.slapping.requesting.claiming.harassing.betting.crying.mending.irritating.concentrating.summarizing.enforcing.exercising.handwriting.supplying.acquiring.purchasing.lecturing.competing.compiling.beating.binding.bleeding.encouraging.charging.tutoring.kissing.testing.fetching.soothsaying.ending.arriving.sipping.appearing.nominating.praying.arranging.overdrawing.hiding.leaving.entering.maintaining.zooming.investigating.tempting.satisfying.suggesting.flowing.extracting.covering.treating.reigning.handling.bathing.upholding.facilitating.swelling.mending.slipping.paddling.mugging.belonging.pinpointing.wailing.pinching.excusing.occurring.ordering.preparing.assessing.parking.transforming.timing.grating.reflecting.groaning.snowing.shutting.checking.organizing.finding.digging.curving.filling.smoking.folding.releasing.locking.finalizing.installing.radiating.crawling.doing.admiring.telephoning.challenging.contracting.puncturing.deserving.owning.peeling.scorching.dispensing.resolving.plugging.disappearing.hypothesizing.parking.interrupting.exhibiting.rejoicing.reorganizing.bouncing.misunderstanding.choosing.deserving.killing.critiquing.mattering.realizing.swimming.passing.transforming.caring.bowing.professing.pinching.appraising.reminding.publicizing.reaching.leaning.knitting.typing.expecting.delivering.choking.seeing.licking.flapping.clarifying.symbolizing.stealing.trapping.drafting"; + +test "game init" { + const ta = std.testing.allocator; + var stream = std.json.TokenStream.init("[\"alice\", \"bob\"]"); + var players = try std.json.parse([]Player, &stream, .{ .allocator = ta }); + defer std.json.parseFree([]Player, players, .{ .allocator = ta }); + + var game: Game = undefined; + try game.init(ta, players); + // defer game.deinit(ta); + + try std.testing.expectEqualStrings(game.current_judge, "alice"); + try std.testing.expect(game.players.len == 2); + try std.testing.expect(game.rounds.len == 0); + + for (game.players) |player| { + try std.testing.expect(player.score == 0); + } + + var data = try game.toJson(ta); + defer ta.free(data); + try game.fromJson(ta, data); + + for ([_]u8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }) |_| { + game.inc_version(); + } + try std.testing.expect(game.version == 10); +} diff --git a/games/punderplay-zig/src/main.zig b/games/punderplay-zig/src/main.zig new file mode 100644 index 0000000..8bed39c --- /dev/null +++ b/games/punderplay-zig/src/main.zig @@ -0,0 +1,100 @@ +const std = @import("std"); +const extism_pdk = @import("extism-pdk"); +const Plugin = extism_pdk.Plugin; +const json = std.json; +const game = @import("game.zig"); + +const VAR_STATE = "state"; + +pub fn main() void {} + +pub fn initGame(allocator: std.mem.Allocator, config: game.Config) !game.Game { + var state: game.Game = undefined; + try state.init(allocator, config.player_ids); + return state; +} + +const LiveEvent = struct { + player_id: game.Player, + event_name: []const u8, + value: EventPayload, +}; + +const EventPayload = struct { + prompt: [2][]const u8, + pun: []const u8, +}; + +const EventUpdate = struct { + state: game.Game, + assigns: Assigns, +}; + +pub fn handleEvent(allocator: std.mem.Allocator, event: LiveEvent, state: *game.Game, update: *EventUpdate) !void { + _ = allocator; + _ = update; + _ = state; + + if (std.mem.eql(u8, event.event_name, "submit-prompt")) {} + + return; +} + +const Assigns = struct { + is_judge: bool, + is_winner: ?bool, + submitted_pun: bool, + current_round: u8, +}; + +pub fn renderView(data: Assigns) i32 { + _ = data; + return 0; +} + +export fn init_game() i32 { + var plugin = Plugin.init(std.heap.wasm_allocator); + const input = plugin.getInput() catch unreachable; + defer plugin.allocator.free(input); + + var stream = json.TokenStream.init(input); + const config = json.parse(game.Config, &stream, .{ .allocator = plugin.allocator }) catch unreachable; + // defer json.parseFree(game.Config, config, .{ .allocator = plugin.allocator }); // TODO: this causes an out of bounds memory access + + var state = initGame(plugin.allocator, config) catch unreachable; + + plugin.setVar(VAR_STATE, state.toJson(plugin.allocator) catch unreachable); + + debugOutput(plugin, state); + return 0; +} + +fn debugOutput(plugin: Plugin, state: game.Game) void { + plugin.output(state.toJson(plugin.allocator) catch unreachable); +} + +export fn handle_event() i32 { + var plugin = Plugin.init(std.heap.wasm_allocator); + const input = plugin.getInput() catch unreachable; + defer plugin.allocator.free(input); + + var stream = json.TokenStream.init(input); + const event = json.parse(LiveEvent, &stream, .{ .allocator = plugin.allocator }) catch unreachable; + defer json.parseFree(LiveEvent, event, .{ .allocator = plugin.allocator }); + + const data = plugin.getVar(VAR_STATE) catch unreachable orelse return -1; + var state: game.Game = undefined; + state.fromJson(plugin.allocator, data) catch unreachable; + + var eventUpdate: EventUpdate = undefined; + handleEvent(plugin.allocator, event, &state, &eventUpdate) catch unreachable; + + plugin.setVar(VAR_STATE, state.toJson(plugin.allocator) catch unreachable); + + return 0; +} + +// export fn render(input: []const u8) i32 { +// const assigns = json.parse(Assigns, input, .{}); +// return renderView(assigns); +// } diff --git a/games/punderplay-zig/src/store.zig b/games/punderplay-zig/src/store.zig new file mode 100644 index 0000000..a70cde0 --- /dev/null +++ b/games/punderplay-zig/src/store.zig @@ -0,0 +1,109 @@ +const std = @import("std"); +const extism_pdk = @import("extism-pdk"); +const Plugin = extism_pdk.Plugin; +const json = std.json; +const game = @import("game.zig"); + +pub const PluginStore = struct { + inner: *Plugin, + + const Self = @This(); + + pub fn init(plugin: *Plugin) Self { + return Self{ .inner = plugin }; + } + + pub fn get(self: Self, key: []const u8) []const u8 { + return self.inner.getVar(key) catch unreachable orelse ""; + } + + pub fn set(self: Self, key: []const u8, value: []const u8) void { + self.inner.setVar(key, value); + } +}; + +pub const MockStore = struct { + inner: std.StringHashMap([]const u8), + + const Self = @This(); + + pub fn init(allocator: std.mem.Allocator) Self { + return Self{ + .inner = std.StringHashMap([]const u8).init(allocator), + }; + } + + pub fn deinit(self: *Self) void { + self.inner.deinit(); + } + + pub fn get(self: Self, key: []const u8) []const u8 { + return self.inner.get(key) orelse ""; + } + + pub fn set(self: *Self, key: []const u8, value: []const u8) void { + self.inner.put(key, value) catch unreachable; + } +}; + +// Thank you to Yigong Liu, and their post[0], without which I'd not have figured this out... +// 0: https://zig.news/yglcode/code-study-interface-idiomspatterns-in-zig-standard-libraries-4lkj +pub const Store = struct { + ptr: *anyopaque, + vtab: *const VTab, + const VTab = struct { + get: *const fn (ptr: *anyopaque, key: []const u8) []const u8, + set: *const fn (ptr: *anyopaque, key: []const u8, value: []const u8) void, + }; + + pub fn get(self: Store, key: []const u8) []const u8 { + return self.vtab.get(self.ptr, key); + } + + pub fn set(self: Store, key: []const u8, value: []const u8) void { + self.vtab.set(self.ptr, key, value); + } + + pub fn init(store: anytype) Store { + const Ptr = @TypeOf(store); + const PtrInfo = @typeInfo(Ptr); + std.debug.assert(PtrInfo == .Pointer); + std.debug.assert(PtrInfo.Pointer.size == .One); + std.debug.assert(@typeInfo(PtrInfo.Pointer.child) == .Struct); + + const alignment = PtrInfo.Pointer.alignment; + const impl = struct { + fn get(ptr: *anyopaque, key: []const u8) []const u8 { + const self = @ptrCast(Ptr, @alignCast(alignment, ptr)); + return self.get(key); + } + + fn set(ptr: *anyopaque, key: []const u8, value: []const u8) void { + const self = @ptrCast(Ptr, @alignCast(alignment, ptr)); + self.set(key, value); + } + }; + + return .{ .ptr = store, .vtab = &.{ + .get = impl.get, + .set = impl.set, + } }; + } +}; + +test "mock store" { + var mock = MockStore.init(std.testing.allocator); + defer mock.deinit(); + + mock.set("some_key", "some_value"); + try std.testing.expectEqualStrings("some_value", mock.get("some_key")); +} + +test "Store with MockStore" { + var m = MockStore.init(std.testing.allocator); + defer m.deinit(); + + var store = Store.init(&m); + store.set("some_key", "some_value"); + try std.testing.expectEqualStrings("some_value", store.get("some_key")); +} diff --git a/games/punderplay-zig/zigmod.lock b/games/punderplay-zig/zigmod.lock new file mode 100644 index 0000000..c4e90b4 --- /dev/null +++ b/games/punderplay-zig/zigmod.lock @@ -0,0 +1,2 @@ +2 +git https://github.com/extism/zig-pdk commit-80821d1ded365580e366339a009d786f016c014c diff --git a/games/punderplay-zig/zigmod.yml b/games/punderplay-zig/zigmod.yml new file mode 100644 index 0000000..9ba9909 --- /dev/null +++ b/games/punderplay-zig/zigmod.yml @@ -0,0 +1,6 @@ +id: f0fex8nt61gpyoimsjcmtpd4ukxh0f2hbts0zgtjntvclua0 +name: punderplay +license: BSD-3 +description: Pun-based game for GameBox demo +root_dependencies: + - src: git https://github.com/extism/zig-pdk