lootsack.c

#include <sys/stat.h>

#include <err.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <time.h>
#include <unistd.h>

#include "lootsack.h"

extern int verbose;

struct treasure_type_limits limits[NUM_TREASURE_TYPES] = {
	{    1,    1,     1,     1 }, /* COIN */
	{   10,  300,    10,    75 }, /* CROWN */
	{    2,   15,     7,    20 }, /* DAGGER */
	{    1, 1500,     1,     3 }, /* GEM */
	{    1, 1200,     3,    15 }, /* NECKLACE */
	{    1,  100,     2,     3 }, /* RING */
	{    3, 1000,   100,  1000 }, /* SWORD */
};

const char *type_names[NUM_TREASURE_TYPES] = {
	"coin",
	"crown",
	"dagger",
	"gem",
	"necklace",
	"ring",
	"sword",
};

const char *type_code_names[NUM_TREASURE_TYPES] = {
	"COIN",
	"CROWN",
	"DAGGER",
	"GEM",
	"NECKLACE",
	"RING",
	"SWORD",
};

uint16_t type_frequency[NUM_TREASURE_TYPES] = {
	/* If a random int between 1 and 1000 is selected, it will be compared
	 * against these values, in order. The first value it finds greater than
	 * or equal to itself will be selected. */
	 900, /* "COIN", */
	 991, /* "CROWN", */
	 994, /* "DAGGER", */
	 995, /* "GEM", */
	 996, /* "NECKLACE", */
	 997, /* "RING", */
	1000, /* "SWORD", */
};

static enum treasure_type match_treasure_type(const char *name);

void
print_treasure_item(struct treasure *t)
{
	printf("%8s weighing %4u and worth %4u gold pieces.\n",
	    type_names[t->t], t->w, t->v);
}

void
print_treasure(struct treasure *all_treasure, size_t n)
{
	size_t i;
	struct treasure *t;
	for (i = 0; i < n; i++) {
		t = all_treasure + i;
		print_treasure_item(t);
	}
}

void print_stats(struct treasure *all_treasure, size_t n)
{
	size_t i;
	struct treasure *t;
	for (i = 0; i < n; i++) {
		t = all_treasure + i;
		printf("%03zu %8s w: %4u, v, %4u rel w/v: %7u.\n",
		    i, type_names[t->t], t->w, t->v, SCALED_W_V_RATIO(t));
	}
}

int
write_treasure_csv(int fd, struct treasure *items, size_t n)
{
	FILE *f;
	struct treasure *t;
	size_t i;

	if (n > TREASURE_ITEM_MAX)
		return -1;

	if ((f = fdopen(fd, "w")) == NULL)
		err(1, "write_treasure_csv:fdopen");

	if (verbose)
		fprintf(stderr, "Writing CSV header line,\n");

	fprintf(f, "TYPE,WEIGHT,VALUE\n");
	for (i = 0; i < n; i++) {
		t = items + i;
		fprintf(f, "%s,%u,%u\n", type_code_names[t->t], t->w, t->v);
	}
	if (verbose)
		fprintf(stderr, "Wrote %zu CSV entries.\n", i);
	return i;
}

int
write_treasure_bin(int fd, struct treasure *t, size_t n)
{
	struct treasure_file_header h;
	struct timespec now;
	size_t i;
	size_t expect;
	ssize_t nb;

	if (n > TREASURE_ITEM_MAX)
		return -1;

	if (clock_gettime(CLOCK_REALTIME, &now) == -1)
		err(1, "clock_gettime");

	h.created_on = now;
	h.num_entries = n;
	h.version = TREASURE_FILE_VERSION;

	/* write the header */
	i = -1;
	expect = sizeof(struct treasure_file_header);
	if ((nb = write(fd, &h, expect)) == -1 || nb == 0)
		goto cleanup;
	i = 0;
	expect = sizeof(struct treasure);
	while (i < n) {
		if ((nb = write(fd, t + i, expect)) == -1 || nb == 0)
			goto cleanup;
		i++;
	}

cleanup:
	close(fd);
	return i;
}

int
read_treasure_bin(int fd, struct treasure_file_header* h,
    struct treasure **out)
{
	ssize_t nr;
	size_t expected;
	size_t i;

	expected = sizeof(struct treasure_file_header);
	/* read the header */
	i = -1;
	if ((nr = read(fd, h, expected)) == -1 || nr == 0)
		goto cleanup;
	/* Ensure version in header matches TREASURE_FILE_VERSION */
	if (h->version != TREASURE_FILE_VERSION)
		goto cleanup;
	/* Ensure number of entries <= TREASURE_ITEM_MAX */
	if (h->num_entries > TREASURE_ITEM_MAX)
		goto cleanup;

	expected = sizeof(struct treasure);
	if ((*out = calloc(h->num_entries, expected)) == NULL)
		err(1, "read_treasure_bin:calloc");
	i = 0;
	while (i < h->num_entries) {
		if ((nr = read(fd, (*out) + i, expected)) == -1 || nr == 0)
			goto cleanup;
		i++;
	}
cleanup:
	close(fd);
	return i;
}

int
read_treasure_csv(int fd, struct treasure_file_header* h, struct treasure **out)
{
	ssize_t linelen;
	size_t linesize, n, lineno;
	struct treasure *t;
	FILE *f;
	char *line, *ptr, *cursor;
	char buf[16];

	
	if ((f = fdopen(fd, "r")) == NULL)
		err(1, "read_treasure_csv:fdopen");

	bzero(h, sizeof(struct treasure_file_header));

	n = 10;
	line = NULL;
	if ((*out = calloc(10, sizeof(struct treasure))) == NULL)
		err(1, "calloc");

	lineno = linesize = 0;
	/* discard header row */
	if ((linelen = getline(&line, &linesize, f)) == -1)
		err(1, "Empty csv file");
	lineno++;

	while (h->num_entries < TREASURE_ITEM_MAX &&
	    ((linelen = getline(&line, &linesize, f)) != -1)) {
		if (h->num_entries == n) {
			if (verbose)
				fprintf(stderr, "Resizing buffer. New n: %zu\n",
				    n * 2);
			/* resize buffer */
			if ((*out = recallocarray(*out, n, n * 2,
			    sizeof(struct treasure))) == NULL)
				err(1, "recallocarray");
			n = n * 2;
		}
		lineno++;
		t = (*out) + h->num_entries;
		/* line will now have the full line, ending with \n\0 */
		/* "TYPE,WEIGHT,VALUE" */
		ptr = strchr(line, ',');
		if (ptr == NULL) {
			if (verbose)
				fprintf(stderr, "Skipping malformed row at "
				    "line: %zu\n", lineno);
			continue;
		} else {
			/* type */
			strlcpy(buf, line, ptr - line + 1);
			if ((t->t = match_treasure_type(buf)) ==
			    NUM_TREASURE_TYPES) {
				if (verbose)
					fprintf(stderr, "Skipping unknown type,"
					    "\"%s\", at line %zu.\n", buf,
					    lineno);
			}
			ptr++;
			cursor = ptr;
		}
		ptr = strchr(cursor, ',');
		if (ptr == NULL) {
			if (verbose)
				fprintf(stderr, "Skipping malformed row at "
				    "line: %zu\n", lineno);
			continue;
		} else {
			/* weight */
			strlcpy(buf, cursor, ptr - cursor + 1);
			t->w = atoll(buf);
			ptr++;
			cursor = ptr;
		}
		/* value */
		t->v = atoll(cursor);
		h->num_entries++;
	}

	free(line);
	return h->num_entries;
}

enum treasure_type
match_treasure_type(const char *name)
{
	     if (strcmp("COIN", name) == 0)     { return COIN; }
	else if (strcmp("CROWN", name) == 0)    { return CROWN; }
	else if (strcmp("DAGGER", name) == 0)   { return DAGGER; }
	else if (strcmp("GEM", name) == 0)      { return GEM; }
	else if (strcmp("NECKLACE", name) == 0) { return NECKLACE; }
	else if (strcmp("RING", name) == 0)     { return RING; }
	else if (strcmp("SWORD", name) == 0)    { return SWORD; }
	else return NUM_TREASURE_TYPES;
}