basic combat loop

This commit is contained in:
Rokas Puzonas 2024-08-01 00:56:33 +03:00
parent 69089a4fb5
commit d7032977a2
2 changed files with 269 additions and 53 deletions

View File

@ -1,4 +1,5 @@
const std = @import("std");
const assert = std.debug.assert;
const json = std.json;
const Allocator = std.mem.Allocator;
@ -116,6 +117,37 @@ pub const Character = struct {
}
};
pub const Inventory = struct {
const slots_count = 20;
pub const InventorySlot = struct {
code: []const u8,
quantity: i64,
fn parse(allocator: Allocator, slot_obj: json.ObjectMap) !InventorySlot {
return InventorySlot{
.code = (try dupeJsonString(allocator, slot_obj, "code")) orelse return error.MissingProperty,
.quantity = getJsonInteger(slot_obj, "quantity") orelse return error.MissingProperty,
};
}
};
slots: [slots_count]InventorySlot,
fn parse(allocator: Allocator, slots_array: json.Array) !Inventory {
assert(slots_array.items.len == Inventory.slots_count);
var inventory: Inventory = undefined;
for (0.., slots_array.items) |i, slot_value| {
const slot_obj = asJsonObject(slot_value) orelse return error.InvalidType;
inventory.slots[i] = try InventorySlot.parse(allocator, slot_obj);
}
return inventory;
}
};
arena: *std.heap.ArenaAllocator,
name: []u8,
@ -146,6 +178,9 @@ pub const Character = struct {
equipment: Equipment,
inventory_max_items: i64,
inventory: Inventory,
fn parse(child_allocator: Allocator, obj: json.ObjectMap) !Character {
var arena = try child_allocator.create(std.heap.ArenaAllocator);
errdefer child_allocator.destroy(arena);
@ -155,6 +190,8 @@ pub const Character = struct {
const allocator = arena.allocator();
const inventory = getJsonArray(obj, "inventory") orelse return error.MissingProperty;
return Character{
.arena = arena,
.account = try dupeJsonString(allocator, obj, "account"),
@ -184,7 +221,10 @@ pub const Character = struct {
.earth = try CombatStats.parse(obj, "attack_earth", "dmg_earth", "res_earth"),
.air = try CombatStats.parse(obj, "attack_air", "dmg_air", "res_air"),
.equipment = try Equipment.parse(allocator, obj)
.equipment = try Equipment.parse(allocator, obj),
.inventory_max_items = getJsonInteger(obj, "inventory_max_items") orelse return error.MissingProperty,
.inventory = try Inventory.parse(allocator, inventory)
};
}
@ -193,6 +233,14 @@ pub const Character = struct {
self.arena.deinit();
child_allocator.destroy(self.arena);
}
pub fn getItemCount(self: *const Character) u32 {
var count: u32 = 0;
for (self.inventory.slots) |slot| {
count += @intCast(slot.quantity);
}
return count;
}
};
pub const CharacterList = struct {
@ -267,8 +315,8 @@ pub const Cooldown = struct {
const reason = getJsonString(obj, "reason") orelse return error.MissingProperty;
return Cooldown{
.allocator = allocator,
.total_seconds = getJsonInteger(obj, "totalSeconds") orelse return error.MissingProperty,
.remaining_seconds = getJsonInteger(obj, "remainingSeconds") orelse return error.MissingProperty,
.total_seconds = getJsonInteger(obj, "total_seconds") orelse return error.MissingProperty,
.remaining_seconds = getJsonInteger(obj, "remaining_seconds") orelse return error.MissingProperty,
.expiration = (try dupeJsonString(allocator, obj, "expiration")) orelse return error.MissingProperty,
.reason = Reason.parse(reason) orelse return error.UnknownReason
};
@ -281,21 +329,73 @@ pub const Cooldown = struct {
pub const FightResult = struct {
cooldown: Cooldown,
character: Character,
fn parse(allocator: Allocator, obj: json.ObjectMap) !FightResult {
const cooldown = getJsonObject(obj, "cooldown") orelse return error.MissingProperty;
const character = getJsonObject(obj, "character") orelse return error.MissingProperty;
return FightResult{
.cooldown = try Cooldown.parse(allocator, cooldown),
.character = try Character.parse(allocator, character)
};
}
pub fn deinit(self: *FightResult) void {
self.cooldown.deinit();
self.character.deinit();
}
};
pub const MoveResult = struct {
cooldown: Cooldown,
fn parse(allocator: Allocator, obj: json.ObjectMap) !MoveResult {
const cooldown = getJsonObject(obj, "cooldown") orelse return error.MissingProperty;
return MoveResult{
.cooldown = try Cooldown.parse(allocator, cooldown)
};
}
pub fn deinit(self: FightResult) void {
pub fn deinit(self: MoveResult) void {
self.cooldown.deinit();
}
};
const ArtifactsFetchResult = struct {
pub const GoldTransactionResult = struct {
cooldown: Cooldown,
fn parse(allocator: Allocator, obj: json.ObjectMap) !GoldTransactionResult {
const cooldown = getJsonObject(obj, "cooldown") orelse return error.MissingProperty;
return GoldTransactionResult{
.cooldown = try Cooldown.parse(allocator, cooldown)
};
}
pub fn deinit(self: GoldTransactionResult) void {
self.cooldown.deinit();
}
};
pub const ItemTransactionResult = struct {
cooldown: Cooldown,
fn parse(allocator: Allocator, obj: json.ObjectMap) !ItemTransactionResult {
const cooldown = getJsonObject(obj, "cooldown") orelse return error.MissingProperty;
return ItemTransactionResult{
.cooldown = try Cooldown.parse(allocator, cooldown)
};
}
pub fn deinit(self: ItemTransactionResult) void {
self.cooldown.deinit();
}
};
pub const ArtifactsFetchResult = struct {
arena: std.heap.ArenaAllocator,
status: std.http.Status,
body: ?json.Value = null,
@ -325,7 +425,7 @@ pub fn init(allocator: Allocator) !ArtifactsAPI {
};
}
fn fetch(self: *ArtifactsAPI, method: std.http.Method, path: []const u8) !ArtifactsFetchResult {
fn fetch(self: *ArtifactsAPI, method: std.http.Method, path: []const u8, payload: ?[]const u8) !ArtifactsFetchResult {
var uri = self.server_uri;
uri.path = .{ .raw = path };
@ -337,7 +437,8 @@ fn fetch(self: *ArtifactsAPI, method: std.http.Method, path: []const u8) !Artifa
var opts = std.http.Client.FetchOptions{
.method = method,
.location = .{ .uri = uri },
.response_storage = .{ .dynamic = &response_storage }
.payload = payload,
.response_storage = .{ .dynamic = &response_storage },
};
var authorization_header: ?[]u8 = null;
@ -349,16 +450,16 @@ fn fetch(self: *ArtifactsAPI, method: std.http.Method, path: []const u8) !Artifa
}
const result = try self.client.fetch(opts);
const response_body = response_storage.items;
if (result.status != .ok) {
std.log.debug("Request {} {s} failed with code {}: {s}", .{method, path, result.status, response_body});
return ArtifactsFetchResult{
.arena = arena,
.status = result.status
};
}
const response_body = response_storage.items;
const parsed = try json.parseFromSliceLeaky(json.Value, arena.allocator(), response_body, .{ .allocate = .alloc_if_needed });
if (parsed != json.Value.object) {
return APIErrors.ParseFailed;
@ -371,6 +472,21 @@ fn fetch(self: *ArtifactsAPI, method: std.http.Method, path: []const u8) !Artifa
};
}
fn fetchAndParseObject(self: *ArtifactsAPI, Result: type, method: std.http.Method, path: []const u8, payload: ?[]const u8) !Result {
const result = try self.fetch(method, path, payload);
defer result.deinit();
if (result.status != .ok) {
return APIErrors.RequestFailed;
}
if (result.body == null) {
return APIErrors.ParseFailed;
}
const body = asJsonObject(result.body.?) orelse return APIErrors.ParseFailed;
return Result.parse(self.allocator, body) catch return APIErrors.ParseFailed;
}
pub fn setServer(self: *ArtifactsAPI, url: []const u8) !void {
const url_dupe = self.allocator.dupe(u8, url);
errdefer self.allocator.free(url_dupe);
@ -448,44 +564,31 @@ fn getJsonObject(object: json.ObjectMap, name: []const u8) ?json.ObjectMap {
return asJsonObject(value.?);
}
fn getJsonArray(object: json.ObjectMap, name: []const u8) ?json.Array {
const value = object.get(name);
if (value == null) {
return null;
}
return asJsonArray(value.?);
}
pub fn getServerStatus(self: *ArtifactsAPI) !ServerStatus {
const result = try self.fetch(.GET, "/");
defer result.deinit();
if (result.status != .ok) {
return APIErrors.RequestFailed;
}
if (result.body == null) {
return APIErrors.ParseFailed;
}
const body = asJsonObject(result.body.?) orelse return APIErrors.ParseFailed;
return ServerStatus.parse(self.allocator, body) catch return APIErrors.ParseFailed;
return try self.fetchAndParseObject(ServerStatus, .GET, "/", null);
}
pub fn getCharacter(self: *ArtifactsAPI, name: []const u8) !Character {
const path = try std.fmt.allocPrint(self.allocator, "/characters/{s}", .{name});
defer self.allocator.free(path);
const result = try self.fetch(.GET, path);
defer result.deinit();
if (result.status != .ok) {
return APIErrors.RequestFailed;
}
if (result.body == null) {
return APIErrors.ParseFailed;
}
const body = asJsonObject(result.body.?) orelse return APIErrors.ParseFailed;
return Character.parse(self.allocator, body) catch return APIErrors.ParseFailed;
return try self.fetchAndParseObject(Character, .GET, path, null);
}
pub fn listMyCharacters(self: *ArtifactsAPI) !CharacterList {
const path = try std.fmt.allocPrint(self.allocator, "/my/characters", .{});
defer self.allocator.free(path);
const result = try self.fetch(.GET, path);
const result = try self.fetch(.GET, path, null);
defer result.deinit();
if (result.status != .ok) {
@ -523,18 +626,37 @@ pub fn actionFight(self: *ArtifactsAPI, name: []const u8) !FightResult {
const path = try std.fmt.allocPrint(self.allocator, "/my/{s}/action/fight", .{name});
defer self.allocator.free(path);
const result = try self.fetch(.POST, path);
defer result.deinit();
return try self.fetchAndParseObject(FightResult, .POST, path, null);
}
if (result.status != .ok) {
return APIErrors.RequestFailed;
}
if (result.body == null) {
return APIErrors.ParseFailed;
}
pub fn actionMove(self: *ArtifactsAPI, name: []const u8, x: i64, y: i64) !MoveResult {
const path = try std.fmt.allocPrint(self.allocator, "/my/{s}/action/move", .{name});
defer self.allocator.free(path);
const body = asJsonObject(result.body.?) orelse return APIErrors.ParseFailed;
return FightResult.parse(self.allocator, body) catch return APIErrors.ParseFailed;
const payload = try std.fmt.allocPrint(self.allocator, "{{ \"x\":{},\"y\":{} }}", .{x, y});
defer self.allocator.free(payload);
return try self.fetchAndParseObject(MoveResult, .POST, path, payload);
}
pub fn actionBankDepositGold(self: *ArtifactsAPI, name: []const u8, quantity: u64) !GoldTransactionResult {
const path = try std.fmt.allocPrint(self.allocator, "/my/{s}/action/bank/deposit/gold", .{name});
defer self.allocator.free(path);
const payload = try std.fmt.allocPrint(self.allocator, "{{ \"quantity\":{} }}", .{quantity});
defer self.allocator.free(payload);
return try self.fetchAndParseObject(GoldTransactionResult, .POST, path, payload);
}
pub fn actionBankDeposit(self: *ArtifactsAPI, name: []const u8, code: []const u8, quantity: u64) !ItemTransactionResult {
const path = try std.fmt.allocPrint(self.allocator, "/my/{s}/action/bank/deposit", .{name});
defer self.allocator.free(path);
const payload = try std.fmt.allocPrint(self.allocator, "{{ \"code\":\"{s}\",\"quantity\":{} }}", .{code, quantity});
defer self.allocator.free(payload);
return try self.fetchAndParseObject(ItemTransactionResult, .POST, path, payload);
}
pub fn deinit(self: *ArtifactsAPI) void {

View File

@ -1,6 +1,43 @@
const std = @import("std");
const assert = std.debug.assert;
const ArtifactsAPI = @import("artifacts.zig");
fn waitForCooldown(remaining_seconds: i64) void {
if (remaining_seconds > 0) {
std.log.debug("Waiting for cooldown {}", .{remaining_seconds});
std.time.sleep(std.time.ns_per_s * (@as(u64, @intCast(remaining_seconds)) + 1));
}
}
pub fn depositIfNeeded(api: *ArtifactsAPI, char: *ArtifactsAPI.Character) !void {
if (char.getItemCount() == char.inventory_max_items) return;
{
const move_result = try api.actionMove(char.name, 4, 1);
defer move_result.deinit();
waitForCooldown(move_result.cooldown.remaining_seconds);
}
const deposit_gold = try api.actionBankDepositGold(char.name, @intCast(char.gold));
defer deposit_gold.deinit();
waitForCooldown(deposit_gold.cooldown.remaining_seconds);
for (char.inventory.slots) |slot| {
if (slot.quantity == 0) continue;
const deposit_item = try api.actionBankDeposit(char.name, slot.code, @intCast(slot.quantity));
defer deposit_item.deinit();
waitForCooldown(deposit_item.cooldown.remaining_seconds);
}
{
const move_result = try api.actionMove(char.name, 0, 1);
defer move_result.deinit();
waitForCooldown(move_result.cooldown.remaining_seconds);
}
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
@ -9,18 +46,75 @@ pub fn main() !void {
var api = try ArtifactsAPI.init(allocator);
defer api.deinit();
{ // Set auth token from environment variable
var env = try std.process.getEnvMap(allocator);
defer env.deinit();
const args = try std.process.argsAlloc(allocator);
defer std.process.argsFree(allocator, args);
const token = env.get("ARTIFACTS_TOKEN");
try api.setToken(token);
if (args.len >= 2) {
const filename = args[1];
const cwd = std.fs.cwd();
var token_buffer: [256]u8 = undefined;
const token = try cwd.readFile(filename, &token_buffer);
try api.setToken(std.mem.trim(u8,token,"\n\t "));
}
while (true) {
const result = try api.actionFight("Daisy");
defer result.deinit();
if (api.token == null) {
return error.MissingToken;
}
std.time.sleep(std.time.ns_per_s * (@as(u64, @intCast(result.cooldown.remaining_seconds)) + 1));
const characters = try api.listMyCharacters();
defer characters.deinit();
{
var longest_cooldown: i64 = 0;
for (characters.items) |char| {
if (char.x == 0 and char.y == 1) continue;
const result = try api.actionMove(char.name, 0, 1);
defer result.deinit();
longest_cooldown = @max(longest_cooldown, result.cooldown.remaining_seconds);
}
if (longest_cooldown > 0) {
std.log.debug("Waiting for cooldown {}", .{longest_cooldown});
std.time.sleep(std.time.ns_per_s * (@as(u64, @intCast(longest_cooldown)) + 1));
}
}
std.log.info("Started main loop", .{});
while (true) {
var results = std.ArrayList(ArtifactsAPI.FightResult).init(allocator);
defer {
for (results.items) |*result| {
result.deinit();
}
results.deinit();
}
for (characters.items) |char| {
var result = try api.actionFight(char.name);
errdefer result.deinit();
try results.append(result);
}
{
var longest_cooldown: i64 = 0;
for (results.items) |result| {
longest_cooldown = @max(longest_cooldown, result.cooldown.remaining_seconds);
}
if (longest_cooldown > 0) {
std.log.debug("Waiting for cooldown {}", .{longest_cooldown});
std.time.sleep(std.time.ns_per_s * (@as(u64, @intCast(longest_cooldown)) + 1));
}
}
for (results.items) |*result| {
try depositIfNeeded(&api, &result.character);
}
}
}