changeset 65:80a913d25aa9

core: implement script, closes #2465 @2h
author David Demelier <markand@malikania.fr>
date Mon, 27 Jan 2020 12:24:20 +0100
parents da9b7462ab92
children 9435a53adab4
files .hgignore Makefile src/adventure/main.c src/core/message.c src/core/script.c src/core/script.h src/core/util.c src/core/util.h tests/test-script.c
diffstat 9 files changed, 526 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Thu Jan 23 20:44:01 2020 +0100
+++ b/.hgignore	Mon Jan 27 12:24:20 2020 +0100
@@ -16,6 +16,7 @@
 ^tests/test-color(\.exe)?$
 ^tests/test-error(\.exe)?$
 ^tests/test-map(\.exe)?$
+^tests/test-script(\.exe)?$
 ^tools/molko-map(\.exe)?$
 
 # doxygen stuff.
--- a/Makefile	Thu Jan 23 20:44:01 2020 +0100
+++ b/Makefile	Mon Jan 27 12:24:20 2020 +0100
@@ -36,6 +36,7 @@
                 src/core/map_state.c \
                 src/core/message.c \
                 src/core/painter.c \
+                src/core/script.c \
                 src/core/sprite.c \
                 src/core/sys.c \
                 src/core/texture.c \
@@ -62,7 +63,8 @@
 
 TESTS=          tests/test-color.c \
                 tests/test-error.c \
-                tests/test-map.c
+                tests/test-map.c \
+                tests/test-script.c
 TESTS_INCS=     -I extern/libgreatest -I src/core ${SDL_CFLAGS}
 TESTS_LIBS=     ${LIB} ${SDL_LDFLAGS} ${LDFLAGS}
 TESTS_OBJS=     ${TESTS:.c=}
--- a/src/adventure/main.c	Thu Jan 23 20:44:01 2020 +0100
+++ b/src/adventure/main.c	Mon Jan 27 12:24:20 2020 +0100
@@ -16,6 +16,10 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
 #include "clock.h"
 #include "error.h"
 #include "event.h"
@@ -31,6 +35,7 @@
 #include "sys.h"
 #include "util.h"
 #include "window.h"
+#include "script.h"
 
 #define WINDOW_WIDTH 1280
 #define WINDOW_HEIGHT 720
@@ -88,7 +93,8 @@
 				return;
 
 			//game_handle(&ev);
-			message_handle(&msg, &ev);
+			if (msg.state)
+				message_handle(&msg, &ev);
 		}
 
 		if (msg.state)
--- a/src/core/message.c	Thu Jan 23 20:44:01 2020 +0100
+++ b/src/core/message.c	Mon Jan 27 12:24:20 2020 +0100
@@ -26,7 +26,7 @@
 #include "texture.h"
 #include "window.h"
 
-#define MESSAGE_SPEED   150     /* Time delay for animations */
+#define MESSAGE_SPEED   200     /* Time delay for animations */
 #define MESSAGE_TIMEOUT 5000    /* Time for auto-closing */
 
 static unsigned int
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/core/script.c	Mon Jan 27 12:24:20 2020 +0100
@@ -0,0 +1,152 @@
+/*
+ * script.c -- convenient sequence of actions
+ *
+ * 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 <stdlib.h>
+#include <string.h>
+
+#include "script.h"
+#include "util.h"
+
+static void
+script_action_finish(struct action *a)
+{
+	assert(a);
+
+	script_finish(a->data);
+	free(a->data);
+}
+
+static void
+script_action_handle(struct action *a, const union event *ev)
+{
+	assert(a);
+	assert(ev);
+
+	script_handle(a->data, ev);
+}
+
+static bool
+script_action_update(struct action *a, unsigned int ticks)
+{
+	assert(a);
+
+	return script_update(a->data, ticks);
+}
+
+static void
+script_action_draw(struct action *a)
+{
+	assert(a);
+
+	script_draw(a->data);
+}
+
+void
+script_init(struct script *s)
+{
+	assert(s);
+
+	memset(s, 0, sizeof (struct script));
+	s->tail = &s->head;
+}
+
+void
+script_start(struct script *s)
+{
+	assert(s);
+
+	s->iter = s->head;
+}
+
+void
+script_append(struct script *s, const struct action *a)
+{
+	assert(s);
+	assert(a);
+	assert(a->update);
+
+	struct script_action *iter = ecalloc(1, sizeof (struct script_action));
+
+	memcpy(&iter->action, a, sizeof (struct action));
+	*s->tail = iter;
+	s->tail = &iter->next;
+}
+
+void
+script_handle(struct script *s, const union event *ev)
+{
+	assert(s);
+	assert(ev);
+
+	if (s->iter)
+		s->iter->action.handle(&s->iter->action, ev);
+}
+
+bool
+script_update(struct script *s, unsigned int ticks)
+{
+	assert(s);
+
+	if (s->iter && s->iter->action.update(&s->iter->action, ticks)) {
+		if (s->iter->action.finish)
+			s->iter->action.finish(&s->iter->action);
+
+		s->iter = s->iter->next;
+	}
+
+	return s->iter == NULL;
+}
+
+void
+script_draw(struct script *s)
+{
+	assert(s);
+
+	if (s->iter && s->iter->action.draw)
+		s->iter->action.draw(&s->iter->action);
+}
+
+void
+script_action(const struct script *s, struct action *action)
+{
+	assert(s);
+	assert(action);
+
+	memset(action, 0, sizeof (struct action));
+	action->data = ememdup(s, sizeof (struct script));
+	action->handle = script_action_handle;
+	action->update = script_action_update;
+	action->draw = script_action_draw;
+	action->finish = script_action_finish;
+}
+
+void
+script_finish(struct script *s)
+{
+	assert(s);
+
+	struct script_action *iter, *next;
+
+	for (iter = s->head; iter; iter = next) {
+		next = iter->next;
+		free(iter);
+	}
+
+	memset(s, 0, sizeof (struct script));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/core/script.h	Mon Jan 27 12:24:20 2020 +0100
@@ -0,0 +1,154 @@
+/*
+ * script.h -- convenient sequence of actions
+ *
+ * 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_SCRIPT_H
+#define MOLKO_SCRIPT_H
+
+/**
+ * \file script.h
+ * \brief Convenient sequence of actions.
+ *
+ * Those routines wrap individual actions into a sequence of actions into an
+ * action itself.
+ *
+ * This is convenient for scenarios where you need to specify several
+ * sequential actions that neet to wait before continuing.
+ *
+ * In a nutshell, to write a scenario you should:
+ *
+ * 1. Create a script with see script_init,
+ * 2. Create one or more actions and append with script_append,
+ * 3. Start the action using script_start,
+ * 4. Put the script into the game using game_add_action.
+ */
+
+#include <stdbool.h>
+
+#include "action.h"
+
+union event;
+
+/**
+ * \brief Single-linked list of actions.
+ */
+struct script_action {
+	struct action action;
+	struct script_action *next;
+};
+
+/**
+ * \brief Sequence of actions and state holder.
+ */
+struct script {
+	struct script_action *iter;     /*!< (RO) Current action */
+	struct script_action *head;     /*!< (RO) Beginning */
+	struct script_action **tail;    /*!< (RO) Pointer to add to tail */
+};
+
+/**
+ * Initialize a script.
+ *
+ * This is mandatory before using any functions, do not zero-initialize the
+ * structure yourself.
+ *
+ * \pre s != NULL
+ * \param s the script
+ */
+void
+script_init(struct script *s);
+
+/**
+ * Call this function before putting the script in the game.
+ *
+ * \pre s != NULL
+ * \param s the script
+ */
+void
+script_start(struct script *s);
+
+/**
+ * Append a new action to the script.
+ *
+ * The action is copied into the script and does not need to be allocated on
+ * the heap.
+ *
+ * The action can be empty but must have at least update member set.
+ *
+ * \pre s != NULL
+ * \pre a != NULL && a->update
+ * \param s the script
+ * \param a the action to copy
+ */
+void
+script_append(struct script *s, const struct action *a);
+
+/**
+ * Handle the event into the current action.
+ *
+ * \pre s != NULL
+ * \pre ev != NULL
+ * \param s the script
+ * \param ev the event
+ * \note You usually don't need to call this yourself.
+ */
+void
+script_handle(struct script *s, const union event *ev);
+
+/**
+ * Update the current action.
+ *
+ * \pre s != NULL
+ * \param s the script
+ * \param ticks the number of milliseconds since last frame
+ * \note You usually don't need to call this yourself.
+ */
+bool
+script_update(struct script *s, unsigned int ticks);
+
+/**
+ * Draw the current action.
+ *
+ * \pre s != NULL
+ * \param s the script
+ * \note You usually don't need to call this yourself.
+ */
+void
+script_draw(struct script *s);
+
+/**
+ * Create an action from the script for use into the game.
+ *
+ * \pre s != NULL
+ * \pre dst != NULL
+ * \param s the script
+ * \param dst the action to build with the script
+ */
+void
+script_action(const struct script *s, struct action *dst);
+
+/**
+ * Destroy all the actions into the script.
+ *
+ * \pre s != NULL
+ * \param s the script
+ * \note You usually don't need to call this yourself.
+ */
+void
+script_finish(struct script *s);
+
+#endif /* !MOLKO_SCRIPT_H */
--- a/src/core/util.c	Thu Jan 23 20:44:01 2020 +0100
+++ b/src/core/util.c	Mon Jan 27 12:24:20 2020 +0100
@@ -47,6 +47,17 @@
 	return mem;
 }
 
+void *
+ememdup(const void *ptr, size_t size)
+{
+	void *mem;
+
+	if (!(mem = malloc(size)))
+		error_fatalf("%s\n", strerror(errno));
+
+	return memcpy(mem, ptr, size);
+}
+
 void
 delay(unsigned int ms)
 {
--- a/src/core/util.h	Thu Jan 23 20:44:01 2020 +0100
+++ b/src/core/util.h	Mon Jan 27 12:24:20 2020 +0100
@@ -51,6 +51,18 @@
 ecalloc(size_t n, size_t size);
 
 /**
+ * Copy the region specified by ptr.
+ *
+ * \pre ptr != NULL
+ * \param ptr the pointer
+ * \param size the size of the memory to copy
+ * \return a pointer
+ * \post returned pointer will never be NULL
+ */
+void *
+ememdup(const void *ptr, size_t size);
+
+/**
  * Put the thread to sleep for a given amount of milliseconds.
  *
  * \param ms the number of milliseconds to wait
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-script.c	Mon Jan 27 12:24:20 2020 +0100
@@ -0,0 +1,185 @@
+/*
+ * test-script.c -- test script action
+ *
+ * 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 <greatest.h>
+
+#include <script.h>
+
+static bool
+truth(struct action *a, unsigned int ticks)
+{
+	(void)a;
+	(void)ticks;
+
+	return true;
+}
+
+static bool
+nope(struct action *a, unsigned int ticks)
+{
+	(void)a;
+	(void)ticks;
+
+	return false;
+}
+
+TEST
+append(void)
+{
+	int ref1 = 0, ref2 = 0;
+	struct script sc;
+
+	script_init(&sc);
+
+	/* ref1 */
+	script_append(&sc, &(struct action) {
+		.update = truth,
+		.data = &ref1
+	});
+
+	ASSERT(sc.head);
+	ASSERT_EQ(sc.head->action.data, &ref1);
+
+	/* ref2 */
+	script_append(&sc, &(struct action) {
+		.update = truth,
+		.data = &ref2
+	});
+
+	ASSERT(sc.head);
+	ASSERT_EQ(sc.head->action.data, &ref1);
+	ASSERT_EQ(sc.head->next->action.data, &ref2);
+
+	script_finish(&sc);
+
+	PASS();
+}
+
+TEST
+finish(void)
+{
+	struct script sc;
+
+	script_init(&sc);
+	script_append(&sc, &(struct action) {
+		.update = truth,
+	});
+	script_append(&sc, &(struct action) {
+		.update = truth,
+	});
+	script_finish(&sc);
+
+	ASSERT(!sc.head);
+	PASS();
+}
+
+SUITE(manipulation)
+{
+	RUN_TEST(append);
+	RUN_TEST(finish);
+}
+
+TEST
+advance(void)
+{
+	int ref1 = 0, ref2 = 0;
+	struct script sc;
+
+	script_init(&sc);
+
+	script_append(&sc, &(struct action) {
+		.update = truth,
+		.data = &ref1
+	});
+	script_append(&sc, &(struct action) {
+		.update = truth,
+		.data = &ref2
+	});
+
+	/* Start -> 1 */
+	script_start(&sc);
+
+	ASSERT(sc.iter);
+	ASSERT_EQ(sc.iter->action.data, &ref1);
+
+	/* Update -> 2 */
+	script_update(&sc, 0);
+
+	ASSERT(sc.iter);
+	ASSERT_EQ(sc.iter->action.data, &ref2);
+
+	/* Update -> end */
+	script_update(&sc, 0);
+
+	ASSERT(!sc.iter);
+
+	script_finish(&sc);
+
+	PASS();
+}
+
+TEST
+dont_advance(void)
+{
+	int ref1 = 0, ref2 = 0;
+	struct script sc;
+
+	script_init(&sc);
+
+	script_append(&sc, &(struct action) {
+		.update = nope,
+		.data = &ref1
+	});
+	script_append(&sc, &(struct action) {
+		.update = truth,
+		.data = &ref2
+	});
+
+	/* Start -> 1 */
+	script_start(&sc);
+
+	ASSERT(sc.iter);
+	ASSERT_EQ(sc.iter->action.data, &ref1);
+
+	/* Update -> 1 */
+	script_update(&sc, 0);
+
+	ASSERT(sc.iter);
+	ASSERT_EQ(sc.iter->action.data, &ref1);
+
+	script_finish(&sc);
+
+	PASS();
+}
+
+SUITE(usage)
+{
+	RUN_TEST(advance);
+	RUN_TEST(dont_advance);
+}
+
+GREATEST_MAIN_DEFS();
+
+int
+main(int argc, char **argv)
+{
+	GREATEST_MAIN_BEGIN();
+	RUN_SUITE(manipulation);
+	RUN_SUITE(usage);
+	GREATEST_MAIN_END();
+}