Mercurial > molko
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(); +}