changeset 85:34e91215ec5a

core: implement basic parts of inventories, closes #2483
author David Demelier <markand@malikania.fr>
date Wed, 11 Mar 2020 19:53:19 +0100
parents a6c2067709ce
children 42fef6046300
files .hgignore Makefile src/core/inventory.c src/core/inventory.h src/core/item.h tests/test-inventory.c
diffstat 6 files changed, 442 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Sat Mar 07 17:06:01 2020 +0100
+++ b/.hgignore	Wed Mar 11 19:53:19 2020 +0100
@@ -15,9 +15,10 @@
 ^molko(\.exe)?$
 ^tests/test-color(\.exe)?$
 ^tests/test-error(\.exe)?$
+^tests/test-inventory(\.exe)?$
 ^tests/test-map(\.exe)?$
+^tests/test-save(\.exe)?$
 ^tests/test-script(\.exe)?$
-^tests/test-save(\.exe)?$
 ^tools/molko-map(\.exe)?$
 
 # doxygen stuff.
--- a/Makefile	Sat Mar 07 17:06:01 2020 +0100
+++ b/Makefile	Wed Mar 11 19:53:19 2020 +0100
@@ -41,6 +41,7 @@
                 src/core/game.c \
                 src/core/image.c \
                 src/core/inhibit.c \
+                src/core/inventory.c \
                 src/core/map.c \
                 src/core/map_state.c \
                 src/core/message.c \
@@ -75,6 +76,7 @@
 
 TESTS=          tests/test-color.c \
                 tests/test-error.c \
+                tests/test-inventory.c \
                 tests/test-map.c \
                 tests/test-save.c \
                 tests/test-script.c
@@ -117,7 +119,7 @@
 ${PROG}: ${LIB} ${ADV_OBJS} ${SQLITE_LIB}
 	${CC} -o $@ ${ADV_OBJS} ${LIB} ${SQLITE_LIB} ${SDL_LDFLAGS} ${LDFLAGS}
 
-${TESTS_OBJS}: ${LIB}
+${TESTS_OBJS}: ${LIB} ${SQLITE_LIB}
 
 tests: ${TESTS_OBJS}
 	for t in $?; do ./$$t; done
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/core/inventory.c	Wed Mar 11 19:53:19 2020 +0100
@@ -0,0 +1,129 @@
+/*
+ * inventory.c -- inventory of items
+ *
+ * Copyright (c) 2020 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <assert.h>
+#include <stddef.h>
+#include <string.h>
+
+#include "inventory.h"
+#include "item.h"
+
+static bool
+can_be_used(struct inventory_slot *slot, const struct item *item)
+{
+	assert(item);
+
+	/* Empty slot. */
+	if (!slot->item)
+		return false;
+
+	/* Not same object. */
+	if (strcmp(slot->item->name, item->name) != 0)
+		return false;
+
+	/* No space in this slot. */
+	if (slot->amount >= slot->item->stackable)
+		return false;
+
+	return true;
+}
+
+static struct inventory_slot *
+find(struct inventory *iv, const struct item *item)
+{
+	assert(iv);
+	assert(item);
+
+	struct inventory_slot *slot = NULL;
+
+	/* First pass: find an entry with the same item. */
+	for (unsigned int r = 0; r < INVENTORY_ROWS_MAX; ++r)
+		for (unsigned int c = 0; c < INVENTORY_COLS_MAX; ++c)
+			if (can_be_used(&iv->items[r][c], item))
+				return &iv->items[r][c];
+
+	/* Second pass: try to find an empty slot. */
+	for (unsigned int r = 0; r < INVENTORY_ROWS_MAX; ++r)
+		for (unsigned int c = 0; c < INVENTORY_COLS_MAX; ++c)
+			if (!iv->items[r][c].item)
+				return &iv->items[r][c];
+
+	return NULL;
+}
+
+static unsigned
+provide(struct inventory_slot *slot, struct item *item, unsigned int amount)
+{
+	assert(slot);
+
+	unsigned int avail;
+
+	/* The slot may be empty, make sure it contains this item. */
+	slot->item = item;
+
+	/*
+	 * Example:
+	 *
+	 * The slot has already 10 items.
+	 * The slot item is stackble up to 64 items.
+	 *
+	 * When pushing:
+	 *
+	 * 80: 54 pushed, 26 left
+	 * 30: 30 pushed, 0 left.
+	 */
+	avail = slot->item->stackable - slot->amount;
+
+	if (amount > avail) {
+		slot->amount += avail;
+		amount -= avail;
+	} else {
+		slot->amount += amount;
+		amount = 0;
+	}
+
+	return amount;
+}
+
+unsigned int
+inventory_push(struct inventory *iv, struct item *item, unsigned int amount)
+{
+	assert(iv);
+	assert(item);
+
+	while (amount) {
+		struct inventory_slot *slot;
+		unsigned int avail;
+
+		if (!(slot = find(iv, item)))
+			break;
+
+		/* Add as much as we can in this slot. */
+		amount = provide(slot, item, amount);
+	}
+
+	return amount;
+}
+
+void
+inventory_clear(struct inventory *iv)
+{
+	assert(iv);
+
+	memset(iv, 0, sizeof (*iv));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/core/inventory.h	Wed Mar 11 19:53:19 2020 +0100
@@ -0,0 +1,84 @@
+/*
+ * inventory.h -- inventory of items
+ *
+ * Copyright (c) 2020 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef MOLKO_INVENTORY_H
+#define MOLKO_INVENTORY_H
+
+/**
+ * \file inventory.h
+ * \brief Inventory of items.
+ */
+
+/**
+ * \brief Maximum number of rows.
+ */
+#define INVENTORY_ROWS_MAX      3
+
+/**
+ * \brief Maximum number of columns.
+ */
+#define INVENTORY_COLS_MAX      10
+
+struct item;
+
+/**
+ * \brief Inventory slot.
+ *
+ * This structure describe a 'cell' into the inventory. It references a item
+ * and has a given amount of it.
+ */
+struct inventory_slot {
+	struct item *item;      /*!< (RO, ref) Pointer to the item. */
+	unsigned int amount;    /*!< (RO) Number of items in this slot. */
+};
+
+/**
+ * \brief Inventory structure.
+ */
+struct inventory {
+	/**
+	 * (RW)
+	 *
+	 * Grid of objects.
+	 */
+	struct inventory_slot items[INVENTORY_ROWS_MAX][INVENTORY_COLS_MAX];
+};
+
+/**
+ * Try to push as much as possible the given item.
+ *
+ * \pre iv != NULL
+ * \pre item != NULL
+ * \param iv the inventory
+ * \param item the item to reference
+ * \param amount the desired amount
+ * \return 0 if all items were pushed or the number left otherwise
+ */
+unsigned int
+inventory_push(struct inventory *iv, struct item *item, unsigned int amount);
+
+/**
+ * Clears the inventory.
+ *
+ * \pre iv != NULL
+ * \param iv the inventory
+ */
+void
+inventory_clear(struct inventory *iv);
+
+#endif /* !MOLKO_INVENTORY_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/core/item.h	Wed Mar 11 19:53:19 2020 +0100
@@ -0,0 +1,65 @@
+/*
+ * item.h -- inventory items
+ *
+ * Copyright (c) 2020 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef MOLKO_ITEM_H
+#define MOLKO_ITEM_H
+
+/**
+ * \file item.h
+ * \brief Inventory items.
+ */
+
+#include <stdbool.h>
+
+/**
+ * \brief Maximum name length.
+ */
+#define ITEM_NAME_MAX   32
+
+/**
+ * \brief Maximum count of an item into a stack.
+ */
+#define ITEM_STACK_MAX  64
+
+struct character;
+struct texture;
+
+/**
+ * \brief Inventory items.
+ */
+struct item {
+	char name[ITEM_NAME_MAX];       /*!< (RW) Name of item. */
+	struct texture *icon;           /*!< (RW, ref) Icon to show. */
+	unsigned int stackable;         /*!< (RW) Stack count allowed. */
+
+	/**
+	 * (RW)
+	 *
+	 * Execute the action for this character.
+	 */
+	void (*exec)(const struct item *, struct character *);
+
+	/**
+	 * (RW, optional)
+	 *
+	 * Tells if the item can be used in this context.
+	 */
+	bool (*allowed)(const struct item *, const struct character *);
+};
+
+#endif /* !MOLKO_ITEM_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-inventory.c	Wed Mar 11 19:53:19 2020 +0100
@@ -0,0 +1,159 @@
+/*
+ * test-inventory.c -- test inventory functions
+ *
+ * Copyright (c) 2020 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define GREATEST_USE_ABBREVS 0
+#include <greatest.h>
+
+#include "item.h"
+#include "inventory.h"
+
+static struct item potion = {
+	.name = "Potion",
+	.stackable = 64
+};
+
+static struct item elixir = {
+	.name = "Elixir",
+	.stackable = 64
+};
+
+GREATEST_TEST
+push_simple(void)
+{
+	struct inventory inv = { 0 };
+	unsigned int left;
+
+	/*
+	 * [0][0] should contain 50 potions.
+	 */
+	left = inventory_push(&inv, &potion, 50U);
+	GREATEST_ASSERT_EQ(left, 0U);
+	GREATEST_ASSERT_EQ(inv.items[0][0].item, &potion);
+	GREATEST_ASSERT_EQ(inv.items[0][0].amount, 50U);
+	inventory_clear(&inv);
+
+	/*
+	 * [0][1] should contain 64 potions.
+	 * [0][2] should contain 10 potions.
+	 */
+	left = inventory_push(&inv, &potion, 74U);
+	GREATEST_ASSERT_EQ(left, 0U);
+	GREATEST_ASSERT_EQ(inv.items[0][0].item, &potion);
+	GREATEST_ASSERT_EQ(inv.items[0][0].amount, 64U);
+	GREATEST_ASSERT_EQ(inv.items[0][1].item, &potion);
+	GREATEST_ASSERT_EQ(inv.items[0][1].amount, 10U);
+	inventory_clear(&inv);
+
+	/*
+	 * 1. We push 10 potions
+	 * 2. We push 10 more potions.
+	 *
+	 * [0][0] should contain 20 potions.
+	 */
+	left = inventory_push(&inv, &potion, 10U);
+	GREATEST_ASSERT_EQ(left, 0U);
+	GREATEST_ASSERT_EQ(inv.items[0][0].item, &potion);
+	GREATEST_ASSERT_EQ(inv.items[0][0].amount, 10U);
+	left = inventory_push(&inv, &potion, 10U);
+	GREATEST_ASSERT_EQ(left, 0U);
+	GREATEST_ASSERT_EQ(inv.items[0][0].item, &potion);
+	GREATEST_ASSERT_EQ(inv.items[0][0].amount, 20U);
+	inventory_clear(&inv);
+
+	GREATEST_PASS();
+}
+
+GREATEST_TEST
+push_full(void)
+{
+	struct inventory inv = { 0 };
+	unsigned int left;
+
+	/*
+	 * Fill the inventory with
+	 *
+	 * - 29 x 64 potions
+	 * - Last slot will have already 50 potions.
+	 *
+	 * This means the maximum potions to push is only 14.
+	 */
+	for (int i = 0; i < 29; ++i) {
+		left = inventory_push(&inv, &potion, 64U);
+		GREATEST_ASSERT_EQ(left, 0U);
+	}
+
+	left = inventory_push(&inv, &potion, 50U);
+	GREATEST_ASSERT_EQ(left, 0U);
+
+	/*
+	 * Try to add 20 potions, only 14 should be pushed.
+	 */
+	left = inventory_push(&inv, &potion, 20U);
+	GREATEST_ASSERT_EQ(left, 6U);
+	GREATEST_ASSERT_EQ(inv.items[2][9].amount, 64U);
+
+	GREATEST_PASS();
+}
+
+GREATEST_TEST
+push_other(void)
+{
+	struct inventory inv = { 0 };
+	unsigned int left;
+
+	/*
+	 * [0][0] should contain 30 potions.
+	 */
+	left = inventory_push(&inv, &potion, 30U);
+	GREATEST_ASSERT_EQ(left, 0U);
+	GREATEST_ASSERT_EQ(inv.items[0][0].item, &potion);
+	GREATEST_ASSERT_EQ(inv.items[0][0].amount, 30U);
+
+	/*
+	 * Since elixir is not a potion, it should be pushed into an other
+	 * cell.
+	 *
+	 * [0][0] should be untouched
+	 * [0][1] should contain 30 elixir.
+	 */
+	left = inventory_push(&inv, &elixir, 30U);
+	GREATEST_ASSERT_EQ(left, 0U);
+	GREATEST_ASSERT_EQ(inv.items[0][0].item, &potion);
+	GREATEST_ASSERT_EQ(inv.items[0][0].amount, 30U);
+	GREATEST_ASSERT_EQ(inv.items[0][1].item, &elixir);
+	GREATEST_ASSERT_EQ(inv.items[0][1].amount, 30U);
+
+	GREATEST_PASS();
+}
+
+GREATEST_SUITE(push)
+{
+	GREATEST_RUN_TEST(push_simple);
+	GREATEST_RUN_TEST(push_full);
+	GREATEST_RUN_TEST(push_other);
+}
+
+GREATEST_MAIN_DEFS();
+
+int
+main(int argc, char **argv)
+{
+	GREATEST_MAIN_BEGIN();
+	GREATEST_RUN_SUITE(push);
+	GREATEST_MAIN_END();
+}