view interps/cfunge/cfunge-src/src/fingerprints/TURT/TURT.c @ 12518:2d8fe55c6e65 draft default tip

<int-e> learn The password of the month is release incident pilot.
author HackEso <hackeso@esolangs.org>
date Sun, 03 Nov 2024 00:31:02 +0000
parents 859f9b4339e6
children
line wrap: on
line source

/* -*- mode: C; coding: utf-8; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*-
 *
 * cfunge - A standard-conforming Befunge93/98/109 interpreter in C.
 * Copyright (C) 2008-2009 Arvid Norlander <anmaster AT tele2 DOT se>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at the proxy's option) any later version. Arvid Norlander is a
 * proxy who can decide which future versions of the GNU General Public
 * License can be used.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "TURT.h"

#if !defined(CFUN_NO_FLOATS) && !defined(CFUN_NO_TURT)
#include "../../stack.h"
#include "../../../lib/genx/genx.h"
#include "../../../lib/stringbuffer/stringbuffer.h"

#include <math.h>    /* cosl, roundl, sinl */
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>   /* fputs, snprintf */
#include <stdlib.h>  /* abs */

// M_PIl is a GNU extension. This value should be enough
// for 128-bit long double.
#ifndef M_PIl
#  define M_PIl 3.1415926535897932384626433832795029L
#endif

#ifndef HAVE_cosl
#  define cosl cos
#endif
#ifndef HAVE_roundl
#  define roundl round
#endif
#ifndef HAVE_sinl
#  define sinl sin
#endif

// This fingerprint is basically a translation from D to C of the TURT
// fingerprint of CCBI, but with a lot of bug fixes.

#define DEFAULT_FILENAME "cfunge_TURT.svg"
static const char* filename = NULL;

/// fixed point with 5 decimals
/// tc meaning "turtle coordinate"
typedef int32_t tc;

// SVGT limits all numbers to -32767.9999 - 32767.9999, not -32768 - 32767
// that limits our width to 32767.9999, hence the min and max values
// we add a PADDING value to get nice viewBoxes for small drawings
#define TURT_PADDING 1
#define TURT_MIN -163839999 + TURT_PADDING
#define TURT_MAX  163839999 - TURT_PADDING

#define FIXEDFMT   "%s%d.%04u"
#define PRINTFIXED(n) ((n) < 0) ? "-" : "", getInt(n), getDec(n)

// For use with genx:
static constUtf8 gns = NULL;

FUNGE_ATTR_FAST FUNGE_ATTR_CONST FUNGE_ATTR_WARN_UNUSED
static inline int getInt(tc c)
{
	return (c < 0 ? -c : c) / 10000;
}

FUNGE_ATTR_FAST FUNGE_ATTR_CONST FUNGE_ATTR_WARN_UNUSED
static inline unsigned int getDec(tc c)
{
	return (unsigned int)(abs(c) % 10000);
}

typedef struct Point {
	tc x, y;
} Point;

typedef struct Turtle {
	/// We use radians here.
	long double heading, sin, cos;
	Point p;
	Point min;
	Point max;
	uint32_t colour;
	bool penDown;
	bool movedWithoutDraw;
} Turtle;

typedef struct Dot {
	Point p;
	uint32_t colour;
} Dot;

typedef struct Path {
	struct Path* next;
	Dot d;
	bool penDown;
} Path;

typedef struct Drawing {
	size_t       dots_size;
	Dot*         dots;
	Path*        pathBeg;
	Path*        path;
	uint32_t     bgColour;
	/**
	 * When possible prefer transparency.
	 * This can be done because TURT specs doesn't say what default is.
	 */
	bool         bgSet;
} Drawing;

static Turtle turt;
static Drawing pic;

FUNGE_ATTR_FAST FUNGE_ATTR_WARN_UNUSED
static inline Path* create_path(Point a, bool b, uint32_t c)
{
	Path* p = cf_malloc(sizeof(Path));
	if (!p)
		return NULL;
	p->next = NULL;
	p->d.p = a;
	p->d.colour = c;
	p->penDown = b;
	return p;
}

FUNGE_ATTR_FAST
static inline void addPath(Point pt, bool penDown, uint32_t colour)
{
	// FIXME: Handle OOM
	Path* p = create_path(pt, penDown, colour);

	if (pic.pathBeg == NULL)
		pic.pathBeg = p;
	else
		pic.path->next = p;
	pic.path = p;
}

FUNGE_ATTR_FAST
static inline void normalise(void)
{
	while (turt.heading > 2*M_PIl)
		turt.heading -= 2 * M_PIl;
	while (turt.heading < 0)
		turt.heading += 2 * M_PIl;
	turt.sin = sinl(turt.heading);
	turt.cos = cosl(turt.heading);
}

FUNGE_ATTR_FAST
static inline void newDraw(void)
{
	if (turt.p.x < turt.min.x)
		turt.min.x = turt.p.x;
	if (turt.p.x > turt.max.x)
		turt.max.x = turt.p.x;

	if (turt.p.y < turt.min.y)
		turt.min.y = turt.p.y;
	if (turt.p.y > turt.max.y)
		turt.max.y = turt.p.y;
}

FUNGE_ATTR_FAST
static inline void move(tc distance)
{
	tc dx, dy;
	int64_t nx, ny;
	long double tmp;

	if (turt.penDown && turt.movedWithoutDraw)
		addPath(turt.p, false, 0);

	tmp = roundl(turt.cos * distance);
	dx = (tc)tmp;
	tmp = roundl(turt.sin * distance);
	dy = (tc)tmp;

	// have to check for under-/overflow...
	nx = turt.p.x + dx;
	if (nx > TURT_MAX)
		nx = TURT_MAX;
	else if (nx < TURT_MIN)
		nx = TURT_MIN;
	turt.p.x = (tc)nx;

	ny = turt.p.y + dy;
	if (ny > TURT_MAX)
		ny = TURT_MAX;
	else if (ny < TURT_MIN)
		ny = TURT_MIN;
	turt.p.y = (tc)ny;

	// a -> ... -> z is equivalent to a -> z if not drawing
	if (turt.penDown) {
		addPath(turt.p, turt.penDown, turt.colour);
		newDraw();
		turt.movedWithoutDraw = false;
	} else
		turt.movedWithoutDraw = true;
}


// helpers...
FUNGE_ATTR_FAST FUNGE_ATTR_CONST FUNGE_ATTR_WARN_UNUSED
static inline long double toRad(funge_cell c)
{
	return (M_PI / 180.0) * c;
}
FUNGE_ATTR_FAST FUNGE_ATTR_CONST FUNGE_ATTR_WARN_UNUSED
static inline funge_cell toDeg(long double r)
{
	long double d = roundl((180.0 / M_PI) * r);
	return (funge_cell)d;
}

FUNGE_ATTR_FAST FUNGE_ATTR_CONST FUNGE_ATTR_WARN_UNUSED
static inline uint32_t toRGB(funge_cell c)
{
	return (uint32_t)(c & ((1 << 24) - 1));
}

FUNGE_ATTR_FAST
static inline void addPoint(void)
{

	for (size_t i = 0; i < pic.dots_size; i++) {
		Dot* dot = &pic.dots[i];
		if ((dot->p.x == turt.p.x) && (dot->p.y == turt.p.y)) {
			if (dot->colour != turt.colour)
				dot->colour = turt.colour;
			return;
		}
	}

	pic.dots_size++;
	// FIXME: Handle OOM.
	pic.dots = cf_realloc(pic.dots, pic.dots_size * sizeof(Dot));
	pic.dots[pic.dots_size - 1].p = turt.p;
	pic.dots[pic.dots_size - 1].colour = turt.colour;
	newDraw();
}

/// If we've moved to a location with the pen up, and the pen is now down, it
/// may be that we'll move to another location with the pen down so there's no
/// need to add a point unless the pen is lifted up or we need to look at the drawing
FUNGE_ATTR_FAST
static inline void tryAddPoint(void)
{
	if (turt.movedWithoutDraw && turt.penDown)
		addPoint();
}

/// Generates a CSS colour
/// Uses a static buffer, not reentrant!
FUNGE_ATTR_FAST
static inline const char* toCSSColour(uint32_t c)
{
	static char s[8];
	snprintf(s, sizeof(s), "#%02x%02x%02x", c >> 16 & 0xff, c >> 8 & 0xff, c & 0xff);
	return s;
}

static inline void freeResources(void)
{
	Path* p = pic.pathBeg;
	if (p) {
		while (p) {
			Path* next = p->next;
			cf_free(p);
			p = next;
		}
		cf_free(p);
	}
	pic.pathBeg = NULL;
	pic.path = NULL;
	cf_free(pic.dots);
	pic.dots = NULL;
	pic.dots_size = 0;
}

/// Print the "header" of the SVG file
FUNGE_ATTR_FAST FUNGE_ATTR_NONNULL
static inline void print_header(genxWriter gw)
{
	tc minx, miny, w, h;

	minx = turt.min.x - TURT_PADDING;
	miny = turt.min.y - TURT_PADDING;
	w = turt.max.x - turt.min.x + 2 * TURT_PADDING;
	h = turt.max.y - turt.min.y + 2 * TURT_PADDING;

	genxStartElementLiteral(gw, gns, (constUtf8)"svg");
	genxAddAttributeLiteral(gw, gns, (constUtf8)"version", (constUtf8)"1.1");
	genxAddAttributeLiteral(gw, gns, (constUtf8)"baseProfile", (constUtf8)"full");
	genxAddAttributeLiteral(gw, gns, (constUtf8)"xmlns", (constUtf8)"http://www.w3.org/2000/svg");
	{
		char sviewbox[256];
		snprintf(sviewbox, sizeof(sviewbox), FIXEDFMT " " FIXEDFMT " " FIXEDFMT " " FIXEDFMT,
		         PRINTFIXED(minx), PRINTFIXED(miny), PRINTFIXED(w), PRINTFIXED(h));
		genxAddAttributeLiteral(gw, gns, (constUtf8)"viewBox", (constUtf8)sviewbox);
	}
	genxStartElementLiteral(gw, gns, (constUtf8)"defs");
	genxStartElementLiteral(gw, gns, (constUtf8)"style");
	genxAddAttributeLiteral(gw, gns, (constUtf8)"type", (constUtf8)"text/css");
	genxAddText(gw,
	            (constUtf8)"path{fill:none;stroke-width:0.00005px;stroke-linecap:round;stroke-linejoin:miter}");
	genxEndElement(gw);
	genxEndElement(gw);
	// This check is because we want transparency if possible.
	if (pic.bgSet) {
		char sminx[64];
		char sminy[64];
		char sw[64];
		char sh[64];
		char scss[sizeof("fill:#112233;stroke:none")];
		snprintf(sminx, sizeof(sminx), FIXEDFMT, PRINTFIXED(minx));
		snprintf(sminy, sizeof(sminy), FIXEDFMT, PRINTFIXED(miny));
		snprintf(sw, sizeof(sw), FIXEDFMT, PRINTFIXED(w));
		snprintf(sh, sizeof(sh), FIXEDFMT, PRINTFIXED(h));
		snprintf(scss, sizeof(scss), "fill:%s;stroke:none", toCSSColour(pic.bgColour));
		genxStartElementLiteral(gw, gns, (constUtf8)"rect");
		genxAddAttributeLiteral(gw, gns, (constUtf8)"style", (constUtf8)scss);
		genxAddAttributeLiteral(gw, gns, (constUtf8)"x", (constUtf8)sminx);
		genxAddAttributeLiteral(gw, gns, (constUtf8)"y", (constUtf8)sminy);
		genxAddAttributeLiteral(gw, gns, (constUtf8)"width", (constUtf8)sw);
		genxAddAttributeLiteral(gw, gns, (constUtf8)"height", (constUtf8)sh);
		genxEndElement(gw);
	}
}

/// Used to print a point in a path element.
FUNGE_ATTR_FAST FUNGE_ATTR_NONNULL
static inline void print_point(StringBuffer * sb, char prefix, tc x, tc y)
{
	stringbuffer_append_printf(sb, "%c" FIXEDFMT "," FIXEDFMT " ", prefix, PRINTFIXED(x), PRINTFIXED(y));
}


/*
 * The actual fingerprint functions
 */

/// A - Query Position (x, y coordinates)
static void finger_TURT_query_heading(instructionPointer * ip)
{
	stack_push(ip->stack, toDeg(turt.heading));
}

/// B - Back (distance in pixels)
static void finger_TURT_back(instructionPointer * ip)
{
	move((tc) - stack_pop(ip->stack));
}

/// C - Pen Colour (24-bit RGB)
static void finger_TURT_pen_colour(instructionPointer * ip)
{
	tryAddPoint();
	turt.colour = toRGB(stack_pop(ip->stack));
}

/// D - Show Display (0 = no, 1 = yes)
static void finger_TURT_show_display(instructionPointer * ip)
{
	// What display? We don't have one as far as I know?
	funge_cell a;
	a = stack_pop(ip->stack);
	switch (a) {
		case 0:  break;
		case 1:  tryAddPoint(); break;
		default: ip_reverse(ip); break;
	}
}

/// E - Query Pen (0 = up, 1 = down)
static void finger_TURT_query_pen(instructionPointer * ip)
{
	stack_push(ip->stack, turt.penDown);
}

/// F - Forward (distance in pixels)
static void finger_TURT_forward(instructionPointer * ip)
{
	move((tc)stack_pop(ip->stack));
}

/// H - Set Heading (angle in degrees, relative to 0deg, east)
static void finger_TURT_set_heading(instructionPointer * ip)
{
	turt.heading = toRad(stack_pop(ip->stack)); normalise();
}


static inline bool generate_path(genxWriter gw, uint32_t colour, const char * path,
                                 genxElement g_path, genxAttribute g_style, genxAttribute g_d)
{
	char sstyle[sizeof("stroke:#112233")];
	snprintf(sstyle, sizeof(sstyle), "stroke:%s", toCSSColour(colour));

	genxStartElement(g_path);
	genxAddAttribute(g_style, (constUtf8)sstyle);
	genxAddAttribute(g_d, (constUtf8)path);
	genxEndElement(gw);
	return true;
}

static inline bool generate_paths(genxWriter gw)
{
	genxElement g_path;
	genxAttribute g_style, g_d;
	genxStatus status;
	Path *p, *prev = NULL;
	StringBuffer * sb;
	char * path_data;
	size_t path_data_length;

	p = pic.pathBeg;
	if (!p)
		return false;

	sb = stringbuffer_new();
	if (!sb)
		return false;
	// Create elements.
	g_path  = genxDeclareElement(gw, NULL, (constUtf8)"path", &status);
	g_style = genxDeclareAttribute(gw, NULL, (constUtf8)"style", &status);
	g_d     = genxDeclareAttribute(gw, NULL, (constUtf8)"d", &status);

	if (p->penDown)
		stringbuffer_append_string(sb, "M0,0 ");

	while (p) {
		// Time to create a new one?
		if (!sb) {
			sb = stringbuffer_new();
			if (!sb)
				return false;
			if (p->penDown)
				print_point(sb, 'M', prev->d.p.x, prev->d.p.y);
		}
		if (p->penDown) {
			print_point(sb, 'L', p->d.p.x, p->d.p.y);
		} else if (p != pic.path) {
			print_point(sb, 'M', p->d.p.x, p->d.p.y);
		}
		if (p->next && (p->d.colour != p->next->d.colour)) {
			path_data = stringbuffer_finish(sb, NULL);
			sb = NULL;
			generate_path(gw, p->d.colour, path_data, g_path, g_style, g_d);
			// TODO: Should we free?
			if (path_data)
				free_nogc(path_data);
			path_data = NULL;
		}
		prev = p;
		p = p->next;
	}
	// Final printout:
	path_data = stringbuffer_finish(sb, &path_data_length);
	if (path_data_length > 0) {
		generate_path(gw, prev->d.colour, path_data, g_path, g_style, g_d);
	}
	if (path_data) free_nogc(path_data);
	return true;
}

static inline bool generate_circle(genxWriter gw, Dot* dot,
                                   genxElement g_circle, genxAttribute g_cx, genxAttribute g_cy,
                                   genxAttribute g_r, genxAttribute g_fill)
{
	char buf[64];
	genxStartElement(g_circle);
	snprintf(buf, sizeof(buf), FIXEDFMT, PRINTFIXED(dot->p.x));
	genxAddAttribute(g_cx, (constUtf8)buf);
	snprintf(buf, sizeof(buf), FIXEDFMT, PRINTFIXED(dot->p.y));
	genxAddAttribute(g_cy, (constUtf8)buf);
	genxAddAttribute(g_r, (constUtf8)"0.000025");
	genxAddAttribute(g_fill, (constUtf8)toCSSColour(dot->colour));
	genxEndElement(gw);
	return true;
}

static inline bool generate_circles(genxWriter gw)
{
	genxStatus status;
	genxElement g_circle;
	genxAttribute g_cx, g_cy, g_r, g_fill;

	// Create elements.
	g_circle = genxDeclareElement(gw, NULL, (constUtf8)"circle", &status);
	g_cx   = genxDeclareAttribute(gw, NULL, (constUtf8)"cx", &status);
	g_cy   = genxDeclareAttribute(gw, NULL, (constUtf8)"cy", &status);
	g_r    = genxDeclareAttribute(gw, NULL, (constUtf8)"r", &status);
	g_fill = genxDeclareAttribute(gw, NULL, (constUtf8)"fill", &status);

	for (size_t i = 0; i < pic.dots_size; i++) {
		if (!generate_circle(gw, &pic.dots[i], g_circle, g_cx, g_cy, g_r, g_fill))
			return false;
	}
	return true;
}

/// I - Print current Drawing (if possible)
static void finger_TURT_print_drawing(instructionPointer * ip)
{
	FILE * file;
	genxWriter gw = NULL;

	tryAddPoint();

	file = fopen(filename, "wb");
	if (!file) {
		goto error;
	}

	gw = genxNew();
	if (!gw) {
		goto error;
	}

	fputs("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n", file);
	fputs("<!-- Created with cfunge (http://kuonet.org/~anmaster/cfunge/) -->\n", file);
	fputs("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n", file);

	if (genxStartDocFile(gw, file) != GENX_SUCCESS) {
		goto error;
	}

	print_header(gw);

	generate_paths(gw);
	generate_circles(gw);
	// End <svg>
	genxEndElement(gw);
	if (genxEndDocument(gw) != GENX_SUCCESS) {
		goto error;
	}
	goto exit;

error:
	ip_reverse(ip);
exit:
	if (file)
		fclose(file);
	if (gw)
		genxDispose(gw);
}

/// L - Turn Left (angle in degrees)
static void finger_TURT_turn_left(instructionPointer * ip)
{
	turt.heading -= toRad(stack_pop(ip->stack)); normalise();
}

/// N - Clear Paper with Colour (24-bit RGB)
static void finger_TURT_clear_paper(instructionPointer * ip)
{
	pic.bgColour = toRGB(stack_pop(ip->stack));
	pic.bgSet = true;
	turt.min.x = 0;
	turt.max.x = 0;
	turt.min.y = 0;
	turt.max.y = 0;
	freeResources();
}

/// P - Pen Position (0 = up, 1 = down)
static void finger_TURT_pen_position(instructionPointer * ip)
{
	funge_cell a;
	a = stack_pop(ip->stack);
	switch (a) {
		case 0:
			tryAddPoint();
			turt.penDown = false;
			break;
		case 1:  turt.penDown = true; break;
		default: ip_reverse(ip); break;
	}
}

/// Q - Query Position (x, y coordinates)
static void finger_TURT_query_position(instructionPointer * ip)
{
	stack_push(ip->stack, turt.p.x);
	stack_push(ip->stack, turt.p.y);
}

/// R - Turn Right (angle in degrees)
static void finger_TURT_turn_right(instructionPointer * ip)
{
	turt.heading += toRad(stack_pop(ip->stack)); normalise();
}

/// T - Teleport (x, y coords relative to origin; 00T = home)
static void finger_TURT_teleport(instructionPointer * ip)
{
	tryAddPoint();

	turt.p.y = (tc)stack_pop(ip->stack);
	turt.p.x = (tc)stack_pop(ip->stack);

	turt.movedWithoutDraw = true;
}

/// U - Query Bounds (two pairs of x, y coordinates)
static void finger_TURT_query_bounds(instructionPointer * ip)
{
	stack_push(ip->stack, TURT_MIN);
	stack_push(ip->stack, TURT_MIN);
	stack_push(ip->stack, TURT_MAX);
	stack_push(ip->stack, TURT_MAX);
}

static bool turt_initialised = false;

static void initialise(void)
{
	if (!turt_initialised) {
		turt_initialised = true;
		filename = DEFAULT_FILENAME;
		turt.movedWithoutDraw = true;
		// To set up turt.sin/turt.cos.
		normalise();
	}
}

bool finger_TURT_load(instructionPointer * ip)
{
	initialise();
#ifndef NDEBUG
	atexit(&freeResources);
#endif
	manager_add_opcode(TURT, 'A', query_heading)
	manager_add_opcode(TURT, 'B', back)
	manager_add_opcode(TURT, 'C', pen_colour)
	manager_add_opcode(TURT, 'D', show_display)
	manager_add_opcode(TURT, 'E', query_pen)
	manager_add_opcode(TURT, 'F', forward)
	manager_add_opcode(TURT, 'H', set_heading)
	manager_add_opcode(TURT, 'I', print_drawing)
	manager_add_opcode(TURT, 'L', turn_left)
	manager_add_opcode(TURT, 'N', clear_paper)
	manager_add_opcode(TURT, 'P', pen_position)
	manager_add_opcode(TURT, 'Q', query_position)
	manager_add_opcode(TURT, 'R', turn_right)
	manager_add_opcode(TURT, 'T', teleport)
	manager_add_opcode(TURT, 'U', query_bounds)
	return true;
}
#endif /* !defined(CFUN_NO_FLOATS) && !defined(CFUN_NO_TURT) */