previndexinfo

code guessing, round #60, stage 2 (guessing)

started at ; stage 2 since . guess by

specification

yawn. the challenge is to calculate line of sight. submissions may be written in any language.

this problem is open-ended. the exact notion of "line of sight" you are implementing is up to you.

your challenge, given an environment and a point in it, is to calculate which parts of the environment are visible from the given point. as any language is allowed, there is no fixed API.

good night.

players

  1. Dolphy
  2. kimapr
  3. kotnen
  4. LyricLy
  5. Olivia
  6. theqwertiest
  7. yui
  8. ✧*.𝓡𝓮𝓲 .*✧

entries

you can download all the entries

entry #1

comments 0

post a comment


cg.zip Zip archive data, at least v1.0 to extract, compression method=store
dir cg
dir src
app.cc ASCII text
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
#include "app.hh"

App::App(const char *title, int w, int h, float scale):
	scrW(w / scale),
	scrH(h / scale),
	scrSize(scrW * scrH),
	pixels(new std::uint32_t[scrSize]),

	winW(w),
	winH(h),
	aspectRatio((float)h / w),

	running(true),
	world(10, 10, Camera(Vec2f(3, 8), M_PI * 1.75, 70, 0.2))
{
	// SDL2 setup
	if (SDL_Init(SDL_INIT_VIDEO) < 0) {
		std::cerr << "Failed to initialize SDL2: " << SDL_GetError() << std::endl;
		std::exit(EXIT_FAILURE);
	}

	win = SDL_CreateWindow(title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
	                       w, h, SDL_WINDOW_SHOWN);
	if (win == NULL) {
		std::cerr << "Failed to create window: " << SDL_GetError() << std::endl;
		std::exit(EXIT_FAILURE);
	}

	ren = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
	if (ren == NULL) {
		std::cerr << "Failed to create renderer: " << SDL_GetError() << std::endl;
		std::exit(EXIT_FAILURE);
	}

	tex = SDL_CreateTexture(ren, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING,
	                        scrW, scrH);
	if (tex == NULL) {
		std::cerr << "Failed to create screen texture: " << SDL_GetError() << std::endl;
		std::exit(EXIT_FAILURE);
	}

	keyboard = SDL_GetKeyboardState(NULL);

	// A hack to force i3wm/sway to float the window on startup
	SDL_SetWindowResizable(win, SDL_TRUE);

	// Init x dir map
	xDirMap.resize(scrW);

	double centerX   = (double)(scrW / 2);
	double planeDist = centerX / std::tan(degToRad(world.cam.fov / 2));
	for (int x = 0; x < scrW; ++ x)
		xDirMap[x] = std::atan2((double)x - centerX, planeDist);

	// Setup map
	world.addLines({
		Linef(2, 3, 3, 2),
		Linef(2, 3, 2, 4),
		Linef(2, 3, 1.8, 3.5),
		Linef(2, 4, 1.8, 3.5),
		Linef(4, 2, 3, 2),
		Linef(4, 2, 5, 3),
		Linef(5, 3, 5, 4),
		Linef(5, 4, 4, 5),
		Linef(4, 5, 3, 5),
		Linef(3, 5, 3.5, 5.2),
		Linef(4, 5, 3.5, 5.2),

		Linef(7, 7, 7, 6),
		Linef(6.5, 7, 6.5, 6),
		Linef(6.5, 7, 7, 7),
		Linef(6.5, 6, 7, 6),

		Linef(6.5, 6, 7.8, 8),
		Linef(6.5, 6, 7.4, 7),
		Linef(7.8, 8, 7.4, 7),

		Linef(0, 6, 2, 9),
		Linef(0, 5, 2, 9),

		Linef(8, 2, 7.2, 3.2),
		Linef(6, 4, 7.2, 3.2),
		Linef(8, 2, 6.8, 2.8),
		Linef(6, 4, 6.8, 2.8),

		Linef(8, 4, 7.2, 2.8),
		Linef(6, 2, 7.2, 2.8),
		Linef(8, 4, 6.8, 3.2),
		Linef(6, 2, 6.8, 3.2),
	});
}

App::~App() {
	free(pixels);
	SDL_DestroyTexture(tex);
	SDL_DestroyRenderer(ren);
	SDL_DestroyWindow(win);

	SDL_Quit();
}

void App::run() {
	uint64_t last, now = SDL_GetPerformanceCounter();
	while (running) {
		last = now;
		now  = SDL_GetPerformanceCounter();
		double dt = (double)(now - last) * 1000 / (double)SDL_GetPerformanceFrequency();

		// Render
		SDL_SetRenderDrawColor(ren, 0, 0, 0, SDL_ALPHA_OPAQUE);
		SDL_RenderClear(ren);

		std::memset(pixels, 0, scrSize * sizeof(*pixels));
		render(dt);
		SDL_UpdateTexture(tex, NULL, pixels, scrW * sizeof(*pixels));

		// Display
		float winAspectRatio = (float)winH / winW;
		viewport.w = winAspectRatio < aspectRatio? winH / aspectRatio : winW;
		viewport.h = winAspectRatio > aspectRatio? winW * aspectRatio : winH;
		viewport.x = winW / 2 - viewport.w / 2;
		viewport.y = winH / 2 - viewport.h / 2;
		SDL_RenderCopy(ren, tex, NULL, &viewport);
		SDL_RenderPresent(ren);

		// Handle events
		SDL_Event evt;
		while (SDL_PollEvent(&evt))
			handleEvent(evt);

		update(dt);
	}
}

void App::render(double dt) {
	(void)dt;

	double fog = 14;

	for (int x = 0; x < scrW; ++ x) {
		RaycastResult result = world.raycast(xDirMap[x]);

		double dirCos = std::cos(xDirMap[x]);
		double dist   = distance(world.cam.pos, result.hit);
		int    wallH  = std::round((double)scrH / (dist * dirCos));
		int    wallY  = scrH / 2 - wallH / 2;

		if (wallH > scrH) wallH = scrH;
		if (wallY < 0)    wallY = 0;

		// Render wall
		float t     = std::max(1.0 - (result.shade * 3 + dist) / fog, 0.0);
		auto  color = (std::uint32_t)(t * 255);
		for (int yo = 0; yo < wallH; ++ yo)
			pixels[(yo + wallY) * scrW + x] = color << 24;

		// Render floor
		for (int yo = wallH / 2; yo < scrH / 2; ++ yo) {
			double dist = (double)scrH / 2 / yo / dirCos;
			Vec2i p(world.cam.pos + Vec2f::fromDir(world.cam.dir + xDirMap[x]) * dist);

			float t     = std::max(1.0 - dist / fog, 0.0);
			auto  color = (std::uint32_t)(t * (p.x % 2 == (p.y % 2 == 0)? 170 : 70));

			pixels[(yo + scrH / 2) * scrW + x] = color << 24 | color << 16 | color << 8;
		}
	}
}

template<typename T>
bool lineIntersectsRect(const Line<T> &l, const Vec2<T> &p1, const Vec2<T> &p2, Vec2<T> &i) {
	std::array<Line<T>, 4> sides = {
		Line<T>(p1.x, p1.y, p1.x, p2.y),
		Line<T>(p1.x, p1.y, p2.x, p1.y),
		Line<T>(p2.x, p2.y, p1.x, p2.y),
		Line<T>(p2.x, p2.y, p2.x, p1.y),
	};

	for (const auto &side : sides) {
		if (l.intersects(side, i))
			return true;
	}
	return false;
}

void App::update(double dt) {
	Vec2f  prev   = world.cam.pos;
	double moveBy = 0.005 * dt;

	if (keyboard[SDL_SCANCODE_W]) world.cam.moveForward(moveBy);
	if (keyboard[SDL_SCANCODE_S]) world.cam.moveBackward(moveBy);
	if (keyboard[SDL_SCANCODE_A]) world.cam.moveLeft(moveBy);
	if (keyboard[SDL_SCANCODE_D]) world.cam.moveRight(moveBy);
	if (keyboard[SDL_SCANCODE_Q]) world.cam.turnLeft(moveBy);
	if (keyboard[SDL_SCANCODE_E]) world.cam.turnRight(moveBy);

	Vec2f p1 = world.cam.pos - world.cam.radius;
	Vec2f p2 = world.cam.pos + world.cam.radius;

	// Collision with lines
	for (const auto &l : world.lines) {
		Vec2f i;
		if (lineIntersectsRect(l, p1, p2, i)) {
			world.cam.pos = prev;
			break;
		}
	}

	// Collision with borders
	if (p1.x < 0)       world.cam.pos.x = world.cam.radius;
	if (p1.y < 0)       world.cam.pos.y = world.cam.radius;
	if (p2.x > world.w) world.cam.pos.x = world.w - world.cam.radius;
	if (p2.y > world.h) world.cam.pos.y = world.h - world.cam.radius;
}

void App::handleEvent(SDL_Event &evt) {
	switch (evt.type) {
	case SDL_QUIT: running = false; break;
	case SDL_WINDOWEVENT:
		if (evt.window.event == SDL_WINDOWEVENT_RESIZED) {
			winW = evt.window.data1;
			winH = evt.window.data2;
		}
		break;
	}
}
app.hh ASCII text
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#ifndef APP_HH_HEADER_GUARD
#define APP_HH_HEADER_GUARD

#include <iostream>      // std::cerr
#include <cstdlib>       // std::exit, EXIT_FAILURE
#include <cstring>       // std::memset
#include <cstdint>       // std::uint32_t, std::uint8_t
#include <unordered_map> // std::unordered_map
#include <string>        // std::string
#include <algorithm>     // std::max

#include <SDL2/SDL.h>

#include "math.hh"
#include "world.hh"

class App {
public:
	App(const char *title, int w, int h, float scale);
	~App();

	void run();

private:
	void render(double dt);
	void update(double dt);
	void handleEvent(SDL_Event &evt);

	SDL_Window    *win;
	SDL_Renderer  *ren;
	SDL_Texture   *tex;
	int            scrW, scrH, scrSize;
	std::uint32_t *pixels;
	SDL_Rect       viewport;

	const std::uint8_t *keyboard;

	int   winW, winH;
	float aspectRatio;
	bool  running;

	World world;

	// Screen x to ray direction map
	std::vector<double> xDirMap;
};

#endif
main.cc ASCII text
1
2
3
4
5
6
7
8
9
#include "app.cc"
#include "world.cc"

int main() {
	App *app = new App("Raycaster", 700, 450, 1);
	app->run();
	delete app;
	return 0;
}
math.hh ASCII text
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#ifndef MATH_HH_HEADER_GUARD
#define MATH_HH_HEADER_GUARD

#include <cmath>     // std::sqrt, std::sqrt, std::round, std::cos, std::sin, std::pow
#include <algorithm> // std::swap
#include <array>     // std::array

#ifndef M_PI
#	define M_PI 3.1415926535
#endif

constexpr double lineBoundaryOffset = 0.00005;

template<typename T>
struct Vec2 {
	T x, y;

	static Vec2<T> fromDir(T dir) {
		return Vec2<T>(std::cos(dir), std::sin(dir));
	}

	Vec2():         x(0), y(0) {}
	Vec2(T a):      x(a), y(a) {}
	Vec2(T a, T b): x(a), y(b) {}

#define funcTemplate(OP)                    \
	Vec2 operator OP(const Vec2 &p) const { \
		return Vec2<T>(x OP p.x, y OP p.y); \
	}

	funcTemplate(+)
	funcTemplate(-)
	funcTemplate(*)
	funcTemplate(/)

#undef  funcTemplate
#define funcTemplate(OP)                \
	Vec2 &operator OP (const Vec2 &p) { \
		x OP p.x;                       \
		y OP p.y;                       \
		return *this;                   \
	}

	funcTemplate(+=)
	funcTemplate(-=)
	funcTemplate(*=)
	funcTemplate(/=)

#undef  funcTemplate
#define funcTemplate(T2)               \
	operator Vec2<T2>() const {        \
		return Vec2<T2>((T2)x, (T2)y); \
	}

	funcTemplate(float)
	funcTemplate(int)

#undef  funcTemplate
#define funcTemplate(NAME, FUNC)          \
	Vec2<T> NAME() const {                \
		return Vec2<T>(FUNC(x), FUNC(y)); \
	}

	funcTemplate(round, std::round)
	funcTemplate(ceil,  std::ceil)
	funcTemplate(floor, std::floor)

#undef funcTemplate

	bool operator ==(const Vec2 &p) {
		return x == p.x and y == p.y;
	}

	bool operator !=(const Vec2 &p) {
		return x != p.x or y != p.y;
	}

	Vec2<T> swap() const {
		return Vec2(y, x);
	}

	Vec2<T> round(double precision) const {
		precision = std::pow(10, precision);
		return Vec2<T>(std::round(x * precision) / precision,
		               std::round(y * precision) / precision);
	}

	friend std::ostream &operator <<(std::ostream &stream, const Vec2 &p) {
		stream << '{' << p.x << ", " << p.y << '}';
	    return stream;
	}
};

using Vec2f = Vec2<double>;
using Vec2i = Vec2<int>;

template<typename T>
struct Line {
	Vec2<T> a, b;

	Line() {}
	Line(Vec2<T> a, Vec2<T> b):   a(a),      b(b) {}
	Line(T x1, T y1, T x2, T y2): a(x1, y1), b(x2, y2) {}

	bool isPointBetweenVertices(const Vec2<T> &p, double boundary) const {
		Vec2<T> p1 = a, p2 = b;

		if (p1.x > p2.x)
			std::swap(p1.x, p2.x);
		if (p.x > p2.x + boundary || p.x < p1.x - boundary)
			return false;

		if (p1.y > p2.y)
			std::swap(p1.y, p2.y);
		if (p.y > p2.y + boundary || p.y < p1.y - boundary)
			return false;

		return true;
	}

	bool intersects(const Line<T> &l, Vec2<T> &i) const {
		double a1 = b.y - a.y;
		double b1 = a.x - b.x;
		double c1 = a1 * a.x + b1 * a.y;

		double a2 = l.b.y - l.a.y;
		double b2 = l.a.x - l.b.x;
		double c2 = a2 * l.a.x + b2 * l.a.y;

		double determinant = a1 * b2 - a2 * b1;
		if (determinant == 0)
			return false;

		i.x = (b2 * c1 - b1 * c2) / determinant;
		i.y = (a1 * c2 - a2 * c1) / determinant;
		return isPointBetweenVertices(i, lineBoundaryOffset) &&
		       l.isPointBetweenVertices(i, lineBoundaryOffset);
	}
};

using Linef = Line<double>;

template<typename T>
double distance(const Vec2<T> &a, const Vec2<T> &b) {
	return std::sqrt(std::pow(a.x - b.x, 2) + std::pow(a.y - b.y, 2));
}

inline double degToRad(double deg) {
	return deg * M_PI / 180;
}

inline double lerp(float a, float b, float f) {
	return a + f * (b - a);
}

#endif
world.cc ASCII text
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
#include "world.hh"

Tile::Tile() {}

Camera::Camera(Vec2f pos, double dir, double fov, double radius):
	pos(pos),
	dir(dir),
	fov(fov),
	radius(radius)
{}

void Camera::moveForward(double scale) {
	pos += Vec2f::fromDir(dir) * scale;
}

void Camera::moveBackward(double scale) {
	pos -= Vec2f::fromDir(dir) * scale;
}

void Camera::moveLeft(double scale) {
	pos += Vec2f::fromDir(dir).swap() * Vec2f(scale, -scale);
}

void Camera::moveRight(double scale) {
	pos -= Vec2f::fromDir(dir).swap() * Vec2f(scale, -scale);
}

void Camera::turnLeft(double scale) {
	dir -= scale;
	sanitizeDir();
}

void Camera::turnRight(double scale) {
	dir += scale;
	sanitizeDir();
}

void Camera::sanitizeDir() {
	while (dir > M_PI * 2) dir -= M_PI * 2;
	while (dir < 0)        dir += M_PI * 2;
}

World::World(int w, int h, const Camera &cam):
	w(w),
	h(h),

	cam(cam)
{
	tiles.resize(h);
	for (auto &row : tiles)
		row.resize(w);
}

const Tile &World::tileAt(int x, int y) const {
	return tiles[y][x];
}

void World::initDDA(Vec2f start, Vec2f d, Vec2f &m,
                    Vec2f &l, Vec2f &ul, Vec2i &p, Vec2i &step) const {
	m  = Vec2f(d.x / d.y, d.y / d.x);
	ul = Vec2f(std::hypot(1, m.y), std::hypot(m.x, 1));
	p  = Vec2i(start);

	if (d.x < 0) {
		step.x = -1;
		l.x    = (start.x - p.x) * ul.x;
	} else {
		step.x = 1;
		l.x    = (p.x + 1 - start.x) * ul.x;
	}

	if (d.y < 0) {
		step.y = -1;
		l.y    = (start.y - p.y) * ul.y;
	} else {
		step.y = 1;
		l.y    = (p.y + 1 - start.y) * ul.y;
	}
}

void World::findClosestLineIntersect(const Vec2i &p, const Linef &line,
                                     Vec2f &closestIntersect, const Linef *&closestLine) const {
	closestLine = NULL;

	double closestDist = -1;
	for (const auto *l : tiles[p.y][p.x].lines) {
		Vec2f i;
		bool intersects = line.intersects(*l, i);
		if (intersects &&
		    i.x >= p.x - lineBoundaryOffset     && i.y >= p.y - lineBoundaryOffset &&
		    i.x <  p.x + lineBoundaryOffset + 1 && i.y <  p.y + lineBoundaryOffset + 1) {
			double dist = distance(line.a, i);
			if (dist < closestDist || closestDist < 0) {
				closestDist      = dist;
				closestIntersect = i;
				closestLine      = l;
			}
		}
	}
}

RaycastResult World::raycast(double off) const {
	Vec2f d(Vec2f::fromDir(cam.dir + off));

	Vec2f m, l, ul;
	Vec2i p, step;
	initDDA(cam.pos, d, m, l, ul, p, step);

	double dist = 0;
	RaycastResult result;
	while (true) {
		if (!tiles[p.y][p.x].lines.empty()) {
			Linef ray(cam.pos, cam.pos + d * (dist + 2));

			const Linef *line;
			findClosestLineIntersect(p, ray, result.hit, line);
			if (line != NULL) {
				result.shade = std::fabs(line->a.y - line->b.y) / distance(line->a, line->b);
				return result;
			}
		}

		if (l.x < l.y) {
			dist = l.x;
			p.x += step.x;
			l.x += ul.x;

			result.shade = 1;
		} else {
			dist = l.y;
			p.y += step.y;
			l.y += ul.y;

			result.shade = 0;
		}

		if (p.x >= w || p.y >= h || p.x < 0 || p.y < 0)
			break;
	}

	result.hit = cam.pos + d * dist;
	return result;
}

void World::addLines(const std::vector<Linef> &toAdd) {
	lines.insert(lines.end(), toAdd.begin(), toAdd.end());
	mapLinesToTiles();
}

void World::mapLineToTiles(const Linef &line) {
	Vec2f a = line.a, b = line.b;
	if (a.y > b.y)
		std::swap(a, b);

	double dist = distance(a, b);

	Vec2f o(b.x - a.x, b.y - a.y);

	Vec2f m, l, ul;
	Vec2i p, step;
	initDDA(a, o, m, l, ul, p, step);

	bool skip = false;
	while (true) {
		auto &lines = tiles[p.y][p.x].lines;
		if (!skip && std::find(lines.begin(), lines.end(), &line) == lines.end())
			lines.push_back(&line);

		skip = l.x == l.y;

		if (l.x < l.y) {
			if (l.x > dist)
				break;

			p.x += step.x;
			l.x += ul.x;
		} else {
			if (l.y > dist)
				break;

			p.y += step.y;
			l.y += ul.y;
		}

		if (p.x >= w || p.y >= h || p.x < 0 || p.y < 0)
			break;
	}
}

void World::mapLinesToTiles() {
	for (auto &row : tiles) {
		for (auto &tile : row)
			tile.lines.clear();
	}

	for (const auto &line : lines)
		mapLineToTiles(line);
}
world.hh ASCII text
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#ifndef WORLD_HH_HEADER_GUARD
#define WORLD_HH_HEADER_GUARD

#include <vector>    // std::vector
#include <algorithm> // std::swap, std::find

#include "math.hh"

struct Tile {
	std::vector<const Linef*> lines;

	Tile();
};

struct RaycastResult {
	Vec2f hit;
	float shade;
};

struct Camera {
	Vec2f  pos;
	double dir, fov, radius;

	Camera(Vec2f pos, double dir, double fov, double radius);

	void moveForward (double scale);
	void moveBackward(double scale);
	void moveLeft    (double scale);
	void moveRight   (double scale);

	void turnLeft(double scale);
	void turnRight(double scale);

	void sanitizeDir();
};

class World {
public:
	int w, h;

	std::vector<std::vector<Tile>> tiles;
	std::vector<Linef> lines;

	Camera cam;

	World(int w, int h, const Camera &cam);

	const Tile &tileAt(int x, int y) const;

	RaycastResult raycast(double off) const;

	void addLines(const std::vector<Linef> &toAdd);

private:
	void findClosestLineIntersect(const Vec2i &p, const Linef &line,
	                              Vec2f &closestIntersect, const Linef *&closestLine) const;

	void initDDA(Vec2f start, Vec2f d, Vec2f &m, Vec2f &l, Vec2f &ul, Vec2i &p, Vec2i &step) const;

	void mapLineToTiles(const Linef &line);
	void mapLinesToTiles();
};

#endif
README.txt ASCII text
1
2
3
4
5
Compile using make
Dependency: SDL2
Controls:
  WASD - Movement
  EQ   - Rotate
makefile ASCII text
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
OUT  = app
SRC  = src/main.cc
DEPS = $(wildcard src/*.cc) $(wildcard src/*.hh)

STD   = c++17
LIBS  = -lSDL2
FLAGS = -O3 -g -Wall -Wextra -pedantic -Wno-deprecated-declarations

build: $(OUT)

$(OUT): $(DEPS)
	$(CXX) $(SRC) -std=$(STD) $(FLAGS) $(LIBS) -o $(OUT)

clean:
	-rm $(OUT)

all:
	@echo build, clean

entry #2

comments 0

post a comment


datapack.tar.zst Zstandard compressed data (v0.8+), Dictionary ID: None

entry #3

comments 0

post a comment


inyourwalls.exe PE32 executable (GUI) Intel 80386 Mono/.Net assembly, for MS Windows, 3 sections

entry #4

comments 0

post a comment


line_of_sight.py Unicode text, UTF-8 text
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from random import *
x,y,z,w=[randint(1,18)for _ in"    "]
g=[list("🕳️"*40)for _ in range(20)]
d=abs(z-x)
e=abs(w-y)
s=(x<z)*2-1
t=(y<w)*2-1
D=d-e
while~0:
 g[y][2*x:2*x+2]="👁️"
 if abs(x+y-z-w)<2:break
 if D*2>-e:D-=e;x+=s
 if D*2<d:D+=d;y+=t
for r in g:print(*r,sep="")

entry #5

comments 2
no im really sorry

no im really sorry


:bottom: replying to no im really sorry

actually, i'm the one who should be apologizing more :bottom:


post a comment


sorry.py ASCII text
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def imsorry(really, sorry):
    i = sorry
    while really[i] == 0 and i >= 0:
        really[i] = 2
        i -= 1
    i = sorry + 1
    while really[i] == 0 and i < len(really):
        really[i] = 2
        i += 1
    return really

entry #6

comments 0

post a comment


OneDimensionalLOS.uiua ASCII text, with no line terminators
1
https://uiua.org/pad?src=0_12_0-dev_1__T25lRGltZW5zaW9uYWxMT1Mg4oaQIOKXvzLip7viipzilqHiiaBAIy7iipziioLiiaBALi4KCk9uZURpbWVuc2lvbmFsTE9TICIjLkAuLi4uZy4jIgpPbmVEaW1lbnNpb25hbExPUyAiZy5ALi4uLi4jIyIKT25lRGltZW5zaW9uYWxMT1MgIiMuQC4jIy5nLiMiCk9uZURpbWVuc2lvbmFsTE9TICJnIy4uLi4uLiNAIgo=

entry #7

comments 0

post a comment


los.py ASCII text
1
2
3
# For any point P = (x, y):
#     P can be seen because its an empty plane with no obstructions.
can_see_point = lambda x: lambda y: x

entry #8

comments 0

post a comment


dir floss
dir .git
dir hooks
applypatch-msg.sample ASCII text
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#!/bin/sh
#
# An example hook script to check the commit log message taken by
# applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit.  The hook is
# allowed to edit the commit message file.
#
# To enable this hook, rename this file to "applypatch-msg".

. git-sh-setup
commitmsg="$(git rev-parse --git-path hooks/commit-msg)"
test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"}
:
commit-msg.sample ASCII text
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/sh
#
# An example hook script to check the commit log message.
# Called by "git commit" with one argument, the name of the file
# that has the commit message.  The hook should exit with non-zero
# status after issuing an appropriate message if it wants to stop the
# commit.  The hook is allowed to edit the commit message file.
#
# To enable this hook, rename this file to "commit-msg".

# Uncomment the below to add a Signed-off-by line to the message.
# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
# hook is more suited to it.
#
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"

# This example catches duplicate Signed-off-by lines.

test "" = "$(grep '^Signed-off-by: ' "$1" |
	 sort | uniq -c | sed -e '/^[ 	]*1[ 	]/d')" || {
	echo >&2 Duplicate Signed-off-by lines.
	exit 1
}
fsmonitor-watchman.sample Perl script text
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
#!/usr/bin/perl

use strict;
use warnings;
use IPC::Open2;

# An example hook script to integrate Watchman
# (https://facebook.github.io/watchman/) with git to speed up detecting
# new and modified files.
#
# The hook is passed a version (currently 2) and last update token
# formatted as a string and outputs to stdout a new update token and
# all files that have been modified since the update token. Paths must
# be relative to the root of the working tree and separated by a single NUL.
#
# To enable this hook, rename this file to "query-watchman" and set
# 'git config core.fsmonitor .git/hooks/query-watchman'
#
my ($version, $last_update_token) = @ARGV;

# Uncomment for debugging
# print STDERR "$0 $version $last_update_token\n";

# Check the hook interface version
if ($version ne 2) {
	die "Unsupported query-fsmonitor hook version '$version'.\n" .
	    "Falling back to scanning...\n";
}

my $git_work_tree = get_working_dir();

my $retry = 1;

my $json_pkg;
eval {
	require JSON::XS;
	$json_pkg = "JSON::XS";
	1;
} or do {
	require JSON::PP;
	$json_pkg = "JSON::PP";
};

launch_watchman();

sub launch_watchman {
	my $o = watchman_query();
	if (is_work_tree_watched($o)) {
		output_result($o->{clock}, @{$o->{files}});
	}
}

sub output_result {
	my ($clockid, @files) = @_;

	# Uncomment for debugging watchman output
	# open (my $fh, ">", ".git/watchman-output.out");
	# binmode $fh, ":utf8";
	# print $fh "$clockid\n@files\n";
	# close $fh;

	binmode STDOUT, ":utf8";
	print $clockid;
	print "\0";
	local $, = "\0";
	print @files;
}

sub watchman_clock {
	my $response = qx/watchman clock "$git_work_tree"/;
	die "Failed to get clock id on '$git_work_tree'.\n" .
		"Falling back to scanning...\n" if $? != 0;

	return $json_pkg->new->utf8->decode($response);
}

sub watchman_query {
	my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty')
	or die "open2() failed: $!\n" .
	"Falling back to scanning...\n";

	# In the query expression below we're asking for names of files that
	# changed since $last_update_token but not from the .git folder.
	#
	# To accomplish this, we're using the "since" generator to use the
	# recency index to select candidate nodes and "fields" to limit the
	# output to file names only. Then we're using the "expression" term to
	# further constrain the results.
	my $last_update_line = "";
	if (substr($last_update_token, 0, 1) eq "c") {
		$last_update_token = "\"$last_update_token\"";
		$last_update_line = qq[\n"since": $last_update_token,];
	}
	my $query = <<"	END";
		["query", "$git_work_tree", {$last_update_line
			"fields": ["name"],
			"expression": ["not", ["dirname", ".git"]]
		}]
	END

	# Uncomment for debugging the watchman query
	# open (my $fh, ">", ".git/watchman-query.json");
	# print $fh $query;
	# close $fh;

	print CHLD_IN $query;
	close CHLD_IN;
	my $response = do {local $/; <CHLD_OUT>};

	# Uncomment for debugging the watch response
	# open ($fh, ">", ".git/watchman-response.json");
	# print $fh $response;
	# close $fh;

	die "Watchman: command returned no output.\n" .
	"Falling back to scanning...\n" if $response eq "";
	die "Watchman: command returned invalid output: $response\n" .
	"Falling back to scanning...\n" unless $response =~ /^\{/;

	return $json_pkg->new->utf8->decode($response);
}

sub is_work_tree_watched {
	my ($output) = @_;
	my $error = $output->{error};
	if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) {
		$retry--;
		my $response = qx/watchman watch "$git_work_tree"/;
		die "Failed to make watchman watch '$git_work_tree'.\n" .
		    "Falling back to scanning...\n" if $? != 0;
		$output = $json_pkg->new->utf8->decode($response);
		$error = $output->{error};
		die "Watchman: $error.\n" .
		"Falling back to scanning...\n" if $error;

		# Uncomment for debugging watchman output
		# open (my $fh, ">", ".git/watchman-output.out");
		# close $fh;

		# Watchman will always return all files on the first query so
		# return the fast "everything is dirty" flag to git and do the
		# Watchman query just to get it over with now so we won't pay
		# the cost in git to look up each individual file.
		my $o = watchman_clock();
		$error = $output->{error};

		die "Watchman: $error.\n" .
		"Falling back to scanning...\n" if $error;

		output_result($o->{clock}, ("/"));
		$last_update_token = $o->{clock};

		eval { launch_watchman() };
		return 0;
	}

	die "Watchman: $error.\n" .
	"Falling back to scanning...\n" if $error;

	return 1;
}

sub get_working_dir {
	my $working_dir;
	if ($^O =~ 'msys' || $^O =~ 'cygwin') {
		$working_dir = Win32::GetCwd();
		$working_dir =~ tr/\\/\//;
	} else {
		require Cwd;
		$working_dir = Cwd::cwd();
	}

	return $working_dir;
}
post-update.sample ASCII text
1
2
3
4
5
6
7
8
#!/bin/sh
#
# An example hook script to prepare a packed repository for use over
# dumb transports.
#
# To enable this hook, rename this file to "post-update".

exec git update-server-info
pre-applypatch.sample ASCII text
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#!/bin/sh
#
# An example hook script to verify what is about to be committed
# by applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit.
#
# To enable this hook, rename this file to "pre-applypatch".

. git-sh-setup
precommit="$(git rev-parse --git-path hooks/pre-commit)"
test -x "$precommit" && exec "$precommit" ${1+"$@"}
:
pre-commit.sample ASCII text
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#!/bin/sh
#
# An example hook script to verify what is about to be committed.
# Called by "git commit" with no arguments.  The hook should
# exit with non-zero status after issuing an appropriate message if
# it wants to stop the commit.
#
# To enable this hook, rename this file to "pre-commit".

if git rev-parse --verify HEAD >/dev/null 2>&1
then
	against=HEAD
else
	# Initial commit: diff against an empty tree object
	against=$(git hash-object -t tree /dev/null)
fi

# If you want to allow non-ASCII filenames set this variable to true.
allownonascii=$(git config --type=bool hooks.allownonascii)

# Redirect output to stderr.
exec 1>&2

# Cross platform projects tend to avoid non-ASCII filenames; prevent
# them from being added to the repository. We exploit the fact that the
# printable range starts at the space character and ends with tilde.
if [ "$allownonascii" != "true" ] &&
	# Note that the use of brackets around a tr range is ok here, (it's
	# even required, for portability to Solaris 10's /usr/bin/tr), since
	# the square bracket bytes happen to fall in the designated range.
	test $(git diff-index --cached --name-only --diff-filter=A -z $against |
	  LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
then
	cat <<\EOF
Error: Attempt to add a non-ASCII file name.

This can cause problems if you want to work with people on other platforms.

To be portable it is advisable to rename the file.

If you know what you are doing you can disable this check using:

  git config hooks.allownonascii true
EOF
	exit 1
fi

# If there are whitespace errors, print the offending file names and fail.
exec git diff-index --check --cached $against --
pre-merge-commit.sample ASCII text
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#!/bin/sh
#
# An example hook script to verify what is about to be committed.
# Called by "git merge" with no arguments.  The hook should
# exit with non-zero status after issuing an appropriate message to
# stderr if it wants to stop the merge commit.
#
# To enable this hook, rename this file to "pre-merge-commit".

. git-sh-setup
test -x "$GIT_DIR/hooks/pre-commit" &&
        exec "$GIT_DIR/hooks/pre-commit"
:
pre-push.sample ASCII text
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#!/bin/sh

# An example hook script to verify what is about to be pushed.  Called by "git
# push" after it has checked the remote status, but before anything has been
# pushed.  If this script exits with a non-zero status nothing will be pushed.
#
# This hook is called with the following parameters:
#
# $1 -- Name of the remote to which the push is being done
# $2 -- URL to which the push is being done
#
# If pushing without using a named remote those arguments will be equal.
#
# Information about the commits which are being pushed is supplied as lines to
# the standard input in the form:
#
#   <local ref> <local oid> <remote ref> <remote oid>
#
# This sample shows how to prevent push of commits where the log message starts
# with "WIP" (work in progress).

remote="$1"
url="$2"

zero=$(git hash-object --stdin </dev/null | tr '[0-9a-f]' '0')

while read local_ref local_oid remote_ref remote_oid
do
	if test "$local_oid" = "$zero"
	then
		# Handle delete
		:
	else
		if test "$remote_oid" = "$zero"
		then
			# New branch, examine all commits
			range="$local_oid"
		else
			# Update to existing branch, examine new commits
			range="$remote_oid..$local_oid"
		fi

		# Check for WIP commit
		commit=$(git rev-list -n 1 --grep '^WIP' "$range")
		if test -n "$commit"
		then
			echo >&2 "Found WIP commit in $local_ref, not pushing"
			exit 1
		fi
	fi
done

exit 0
pre-rebase.sample ASCII text
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
#!/bin/sh
#
# Copyright (c) 2006, 2008 Junio C Hamano
#
# The "pre-rebase" hook is run just before "git rebase" starts doing
# its job, and can prevent the command from running by exiting with
# non-zero status.
#
# The hook is called with the following parameters:
#
# $1 -- the upstream the series was forked from.
# $2 -- the branch being rebased (or empty when rebasing the current branch).
#
# This sample shows how to prevent topic branches that are already
# merged to 'next' branch from getting rebased, because allowing it
# would result in rebasing already published history.

publish=next
basebranch="$1"
if test "$#" = 2
then
	topic="refs/heads/$2"
else
	topic=`git symbolic-ref HEAD` ||
	exit 0 ;# we do not interrupt rebasing detached HEAD
fi

case "$topic" in
refs/heads/??/*)
	;;
*)
	exit 0 ;# we do not interrupt others.
	;;
esac

# Now we are dealing with a topic branch being rebased
# on top of master.  Is it OK to rebase it?

# Does the topic really exist?
git show-ref -q "$topic" || {
	echo >&2 "No such branch $topic"
	exit 1
}

# Is topic fully merged to master?
not_in_master=`git rev-list --pretty=oneline ^master "$topic"`
if test -z "$not_in_master"
then
	echo >&2 "$topic is fully merged to master; better remove it."
	exit 1 ;# we could allow it, but there is no point.
fi

# Is topic ever merged to next?  If so you should not be rebasing it.
only_next_1=`git rev-list ^master "^$topic" ${publish} | sort`
only_next_2=`git rev-list ^master           ${publish} | sort`
if test "$only_next_1" = "$only_next_2"
then
	not_in_topic=`git rev-list "^$topic" master`
	if test -z "$not_in_topic"
	then
		echo >&2 "$topic is already up to date with master"
		exit 1 ;# we could allow it, but there is no point.
	else
		exit 0
	fi
else
	not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"`
	/usr/bin/perl -e '
		my $topic = $ARGV[0];
		my $msg = "* $topic has commits already merged to public branch:\n";
		my (%not_in_next) = map {
			/^([0-9a-f]+) /;
			($1 => 1);
		} split(/\n/, $ARGV[1]);
		for my $elem (map {
				/^([0-9a-f]+) (.*)$/;
				[$1 => $2];
			} split(/\n/, $ARGV[2])) {
			if (!exists $not_in_next{$elem->[0]}) {
				if ($msg) {
					print STDERR $msg;
					undef $msg;
				}
				print STDERR " $elem->[1]\n";
			}
		}
	' "$topic" "$not_in_next" "$not_in_master"
	exit 1
fi

<<\DOC_END

This sample hook safeguards topic branches that have been
published from being rewound.

The workflow assumed here is:

 * Once a topic branch forks from "master", "master" is never
   merged into it again (either directly or indirectly).

 * Once a topic branch is fully cooked and merged into "master",
   it is deleted.  If you need to build on top of it to correct
   earlier mistakes, a new topic branch is created by forking at
   the tip of the "master".  This is not strictly necessary, but
   it makes it easier to keep your history simple.

 * Whenever you need to test or publish your changes to topic
   branches, merge them into "next" branch.

The script, being an example, hardcodes the publish branch name
to be "next", but it is trivial to make it configurable via
$GIT_DIR/config mechanism.

With this workflow, you would want to know:

(1) ... if a topic branch has ever been merged to "next".  Young
    topic branches can have stupid mistakes you would rather
    clean up before publishing, and things that have not been
    merged into other branches can be easily rebased without
    affecting other people.  But once it is published, you would
    not want to rewind it.

(2) ... if a topic branch has been fully merged to "master".
    Then you can delete it.  More importantly, you should not
    build on top of it -- other people may already want to
    change things related to the topic as patches against your
    "master", so if you need further changes, it is better to
    fork the topic (perhaps with the same name) afresh from the
    tip of "master".

Let's look at this example:

		   o---o---o---o---o---o---o---o---o---o "next"
		  /       /           /           /
		 /   a---a---b A     /           /
		/   /               /           /
	       /   /   c---c---c---c B         /
	      /   /   /             \         /
	     /   /   /   b---b C     \       /
	    /   /   /   /             \     /
    ---o---o---o---o---o---o---o---o---o---o---o "master"


A, B and C are topic branches.

 * A has one fix since it was merged up to "next".

 * B has finished.  It has been fully merged up to "master" and "next",
   and is ready to be deleted.

 * C has not merged to "next" at all.

We would want to allow C to be rebased, refuse A, and encourage
B to be deleted.

To compute (1):

	git rev-list ^master ^topic next
	git rev-list ^master        next

	if these match, topic has not merged in next at all.

To compute (2):

	git rev-list master..topic

	if this is empty, it is fully merged to "master".

DOC_END
pre-receive.sample ASCII text
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/sh
#
# An example hook script to make use of push options.
# The example simply echoes all push options that start with 'echoback='
# and rejects all pushes when the "reject" push option is used.
#
# To enable this hook, rename this file to "pre-receive".

if test -n "$GIT_PUSH_OPTION_COUNT"
then
	i=0
	while test "$i" -lt "$GIT_PUSH_OPTION_COUNT"
	do
		eval "value=\$GIT_PUSH_OPTION_$i"
		case "$value" in
		echoback=*)
			echo "echo from the pre-receive-hook: ${value#*=}" >&2
			;;
		reject)
			exit 1
		esac
		i=$((i + 1))
	done
fi
prepare-commit-msg.sample ASCII text
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#!/bin/sh
#
# An example hook script to prepare the commit log message.
# Called by "git commit" with the name of the file that has the
# commit message, followed by the description of the commit
# message's source.  The hook's purpose is to edit the commit
# message file.  If the hook fails with a non-zero status,
# the commit is aborted.
#
# To enable this hook, rename this file to "prepare-commit-msg".

# This hook includes three examples. The first one removes the
# "# Please enter the commit message..." help message.
#
# The second includes the output of "git diff --name-status -r"
# into the message, just before the "git status" output.  It is
# commented because it doesn't cope with --amend or with squashed
# commits.
#
# The third example adds a Signed-off-by line to the message, that can
# still be edited.  This is rarely a good idea.

COMMIT_MSG_FILE=$1
COMMIT_SOURCE=$2
SHA1=$3

/usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE"

# case "$COMMIT_SOURCE,$SHA1" in
#  ,|template,)
#    /usr/bin/perl -i.bak -pe '
#       print "\n" . `git diff --cached --name-status -r`
# 	 if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;;
#  *) ;;
# esac

# SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
# git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE"
# if test -z "$COMMIT_SOURCE"
# then
#   /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE"
# fi
push-to-checkout.sample ASCII text
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#!/bin/sh

# An example hook script to update a checked-out tree on a git push.
#
# This hook is invoked by git-receive-pack(1) when it reacts to git
# push and updates reference(s) in its repository, and when the push
# tries to update the branch that is currently checked out and the
# receive.denyCurrentBranch configuration variable is set to
# updateInstead.
#
# By default, such a push is refused if the working tree and the index
# of the remote repository has any difference from the currently
# checked out commit; when both the working tree and the index match
# the current commit, they are updated to match the newly pushed tip
# of the branch. This hook is to be used to override the default
# behaviour; however the code below reimplements the default behaviour
# as a starting point for convenient modification.
#
# The hook receives the commit with which the tip of the current
# branch is going to be updated:
commit=$1

# It can exit with a non-zero status to refuse the push (when it does
# so, it must not modify the index or the working tree).
die () {
	echo >&2 "$*"
	exit 1
}

# Or it can make any necessary changes to the working tree and to the
# index to bring them to the desired state when the tip of the current
# branch is updated to the new commit, and exit with a zero status.
#
# For example, the hook can simply run git read-tree -u -m HEAD "$1"
# in order to emulate git fetch that is run in the reverse direction
# with git push, as the two-tree form of git read-tree -u -m is
# essentially the same as git switch or git checkout that switches
# branches while keeping the local changes in the working tree that do
# not interfere with the difference between the branches.

# The below is a more-or-less exact translation to shell of the C code
# for the default behaviour for git's push-to-checkout hook defined in
# the push_to_deploy() function in builtin/receive-pack.c.
#
# Note that the hook will be executed from the repository directory,
# not from the working tree, so if you want to perform operations on
# the working tree, you will have to adapt your code accordingly, e.g.
# by adding "cd .." or using relative paths.

if ! git update-index -q --ignore-submodules --refresh
then
	die "Up-to-date check failed"
fi

if ! git diff-files --quiet --ignore-submodules --
then
	die "Working directory has unstaged changes"
fi

# This is a rough translation of:
#
#   head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX
if git cat-file -e HEAD 2>/dev/null
then
	head=HEAD
else
	head=$(git hash-object -t tree --stdin </dev/null)
fi

if ! git diff-index --quiet --cached --ignore-submodules $head --
then
	die "Working directory has staged changes"
fi

if ! git read-tree -u -m "$commit"
then
	die "Could not update working tree to new HEAD"
fi
sendemail-validate.sample ASCII text
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#!/bin/sh

# An example hook script to validate a patch (and/or patch series) before
# sending it via email.
#
# The hook should exit with non-zero status after issuing an appropriate
# message if it wants to prevent the email(s) from being sent.
#
# To enable this hook, rename this file to "sendemail-validate".
#
# By default, it will only check that the patch(es) can be applied on top of
# the default upstream branch without conflicts in a secondary worktree. After
# validation (successful or not) of the last patch of a series, the worktree
# will be deleted.
#
# The following config variables can be set to change the default remote and
# remote ref that are used to apply the patches against:
#
#   sendemail.validateRemote (default: origin)
#   sendemail.validateRemoteRef (default: HEAD)
#
# Replace the TODO placeholders with appropriate checks according to your
# needs.

validate_cover_letter () {
	file="$1"
	# TODO: Replace with appropriate checks (e.g. spell checking).
	true
}

validate_patch () {
	file="$1"
	# Ensure that the patch applies without conflicts.
	git am -3 "$file" || return
	# TODO: Replace with appropriate checks for this patch
	# (e.g. checkpatch.pl).
	true
}

validate_series () {
	# TODO: Replace with appropriate checks for the whole series
	# (e.g. quick build, coding style checks, etc.).
	true
}

# main -------------------------------------------------------------------------

if test "$GIT_SENDEMAIL_FILE_COUNTER" = 1
then
	remote=$(git config --default origin --get sendemail.validateRemote) &&
	ref=$(git config --default HEAD --get sendemail.validateRemoteRef) &&
	worktree=$(mktemp --tmpdir -d sendemail-validate.XXXXXXX) &&
	git worktree add -fd --checkout "$worktree" "refs/remotes/$remote/$ref" &&
	git config --replace-all sendemail.validateWorktree "$worktree"
else
	worktree=$(git config --get sendemail.validateWorktree)
fi || {
	echo "sendemail-validate: error: failed to prepare worktree" >&2
	exit 1
}

unset GIT_DIR GIT_WORK_TREE
cd "$worktree" &&

if grep -q "^diff --git " "$1"
then
	validate_patch "$1"
else
	validate_cover_letter "$1"
fi &&

if test "$GIT_SENDEMAIL_FILE_COUNTER" = "$GIT_SENDEMAIL_FILE_TOTAL"
then
	git config --unset-all sendemail.validateWorktree &&
	trap 'git worktree remove -ff "$worktree"' EXIT &&
	validate_series
fi
update.sample ASCII text
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#!/bin/sh
#
# An example hook script to block unannotated tags from entering.
# Called by "git receive-pack" with arguments: refname sha1-old sha1-new
#
# To enable this hook, rename this file to "update".
#
# Config
# ------
# hooks.allowunannotated
#   This boolean sets whether unannotated tags will be allowed into the
#   repository.  By default they won't be.
# hooks.allowdeletetag
#   This boolean sets whether deleting tags will be allowed in the
#   repository.  By default they won't be.
# hooks.allowmodifytag
#   This boolean sets whether a tag may be modified after creation. By default
#   it won't be.
# hooks.allowdeletebranch
#   This boolean sets whether deleting branches will be allowed in the
#   repository.  By default they won't be.
# hooks.denycreatebranch
#   This boolean sets whether remotely creating branches will be denied
#   in the repository.  By default this is allowed.
#

# --- Command line
refname="$1"
oldrev="$2"
newrev="$3"

# --- Safety check
if [ -z "$GIT_DIR" ]; then
	echo "Don't run this script from the command line." >&2
	echo " (if you want, you could supply GIT_DIR then run" >&2
	echo "  $0 <ref> <oldrev> <newrev>)" >&2
	exit 1
fi

if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
	echo "usage: $0 <ref> <oldrev> <newrev>" >&2
	exit 1
fi

# --- Config
allowunannotated=$(git config --type=bool hooks.allowunannotated)
allowdeletebranch=$(git config --type=bool hooks.allowdeletebranch)
denycreatebranch=$(git config --type=bool hooks.denycreatebranch)
allowdeletetag=$(git config --type=bool hooks.allowdeletetag)
allowmodifytag=$(git config --type=bool hooks.allowmodifytag)

# check for no description
projectdesc=$(sed -e '1q' "$GIT_DIR/description")
case "$projectdesc" in
"Unnamed repository"* | "")
	echo "*** Project description file hasn't been set" >&2
	exit 1
	;;
esac

# --- Check types
# if $newrev is 0000...0000, it's a commit to delete a ref.
zero=$(git hash-object --stdin </dev/null | tr '[0-9a-f]' '0')
if [ "$newrev" = "$zero" ]; then
	newrev_type=delete
else
	newrev_type=$(git cat-file -t $newrev)
fi

case "$refname","$newrev_type" in
	refs/tags/*,commit)
		# un-annotated tag
		short_refname=${refname##refs/tags/}
		if [ "$allowunannotated" != "true" ]; then
			echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2
			echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2
			exit 1
		fi
		;;
	refs/tags/*,delete)
		# delete tag
		if [ "$allowdeletetag" != "true" ]; then
			echo "*** Deleting a tag is not allowed in this repository" >&2
			exit 1
		fi
		;;
	refs/tags/*,tag)
		# annotated tag
		if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1
		then
			echo "*** Tag '$refname' already exists." >&2
			echo "*** Modifying a tag is not allowed in this repository." >&2
			exit 1
		fi
		;;
	refs/heads/*,commit)
		# branch
		if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then
			echo "*** Creating a branch is not allowed in this repository" >&2
			exit 1
		fi
		;;
	refs/heads/*,delete)
		# delete branch
		if [ "$allowdeletebranch" != "true" ]; then
			echo "*** Deleting a branch is not allowed in this repository" >&2
			exit 1
		fi
		;;
	refs/remotes/*,commit)
		# tracking branch
		;;
	refs/remotes/*,delete)
		# delete tracking branch
		if [ "$allowdeletebranch" != "true" ]; then
			echo "*** Deleting a tracking branch is not allowed in this repository" >&2
			exit 1
		fi
		;;
	*)
		# Anything else (is there anything else?)
		echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2
		exit 1
		;;
esac

# --- Finished
exit 0
dir info
exclude ASCII text
1
2
3
4
5
6
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
dir logs
dir refs
dir heads
master ASCII text
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
0000000000000000000000000000000000000000 b193587fc2b7b0882d31f4b526e714b08a5006de DolphyWind <yunusaydin590@gmail.com> 1717846319 +1200	commit (initial): Initial commit
b193587fc2b7b0882d31f4b526e714b08a5006de 6659d371bb47f4afe82a997f7de09aef78d6089e DolphyWind <yunusaydin590@gmail.com> 1717850019 +1200	commit: Add gitignore
6659d371bb47f4afe82a997f7de09aef78d6089e be696def459a45122135536e906d647d86772026 DolphyWind <yunusaydin590@gmail.com> 1717850097 +1200	commit: Create grid trait and simple impl
be696def459a45122135536e906d647d86772026 35588af1fe35306264b7564e01d0ac6be8830c53 DolphyWind <yunusaydin590@gmail.com> 1717850121 +1200	commit (amend): Create grid trait and simple impl
35588af1fe35306264b7564e01d0ac6be8830c53 3a0b611d1f4f0d2df4ddbe5b49610903fe837c38 DolphyWind <yunusaydin590@gmail.com> 1717850809 +1200	commit: Make crate a library
3a0b611d1f4f0d2df4ddbe5b49610903fe837c38 3292ed208adf90aafeb3fa06be96de5db267543c DolphyWind <yunusaydin590@gmail.com> 1717850835 +1200	commit: Document ArrayGrid::new
3292ed208adf90aafeb3fa06be96de5db267543c 6e717d1ecacdf7d65dc8286b4bc2bae84196d937 DolphyWind <yunusaydin590@gmail.com> 1717851523 +1200	commit: Publicise and document
6e717d1ecacdf7d65dc8286b4bc2bae84196d937 02ab80302b798f7c77be67a7b48a80d9d0b6d272 DolphyWind <yunusaydin590@gmail.com> 1718303503 +1200	commit: Remove ArrayGrid
02ab80302b798f7c77be67a7b48a80d9d0b6d272 604d2bebbcb2d028c3db5557084c357f1962300a DolphyWind <yunusaydin590@gmail.com> 1718303515 +1200	commit: Implement algorithm
604d2bebbcb2d028c3db5557084c357f1962300a de69d5f9c9859b90411801ffe38421857794a7b3 DolphyWind <yunusaydin590@gmail.com> 1718306653 +1200	commit: Formatting
de69d5f9c9859b90411801ffe38421857794a7b3 08bffdc0431a3fffb8a0ddf25eaa3a0c9149bfc6 DolphyWind <yunusaydin590@gmail.com> 1718314280 +1200	commit: Fix vision cone when against a wall
08bffdc0431a3fffb8a0ddf25eaa3a0c9149bfc6 ada0c1a90f774c2f8f96da45b6542684ae1cef1a DolphyWind <yunusaydin590@gmail.com> 1718314441 +1200	commit: Remove lenience for targeting
ada0c1a90f774c2f8f96da45b6542684ae1cef1a d6f2e25da1a935b7416862e8728892e93d341f21 DolphyWind <yunusaydin590@gmail.com> 1718314456 +1200	commit: Add demonstration program
d6f2e25da1a935b7416862e8728892e93d341f21 51779086d648aacc65219179751b778d9326c7d7 DolphyWind <yunusaydin590@gmail.com> 1718314468 +1200	commit (amend): Add demonstration program
51779086d648aacc65219179751b778d9326c7d7 e5f9ff76148428dc88fb7c82c4d14052f406a1c7 DolphyWind <yunusaydin590@gmail.com> 1718315679 +1200	commit: Add README and model image
e5f9ff76148428dc88fb7c82c4d14052f406a1c7 c7729e0e8c5b5ee524c29940e66ee08a591c7f9b DolphyWind <yunusaydin590@gmail.com> 1718315891 +1200	commit: Formatting
c7729e0e8c5b5ee524c29940e66ee08a591c7f9b 876b2b9fa19a6042539487f068bd3a440dac56df DolphyWind <yunusaydin590@gmail.com> 1718332638 +1200	commit: Simplify handling of origin square vision
876b2b9fa19a6042539487f068bd3a440dac56df 99129412b67ec819c21baad5a51dfcf02becd31e DolphyWind <yunusaydin590@gmail.com> 1718333532 +1200	commit: Fix ability to see slightly through diagonals
HEAD ASCII text
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
0000000000000000000000000000000000000000 b193587fc2b7b0882d31f4b526e714b08a5006de DolphyWind <yunusaydin590@gmail.com> 1717846319 +1200	commit (initial): Initial commit
b193587fc2b7b0882d31f4b526e714b08a5006de 6659d371bb47f4afe82a997f7de09aef78d6089e DolphyWind <yunusaydin590@gmail.com> 1717850019 +1200	commit: Add gitignore
6659d371bb47f4afe82a997f7de09aef78d6089e be696def459a45122135536e906d647d86772026 DolphyWind <yunusaydin590@gmail.com> 1717850097 +1200	commit: Create grid trait and simple impl
be696def459a45122135536e906d647d86772026 35588af1fe35306264b7564e01d0ac6be8830c53 DolphyWind <yunusaydin590@gmail.com> 1717850121 +1200	commit (amend): Create grid trait and simple impl
35588af1fe35306264b7564e01d0ac6be8830c53 3a0b611d1f4f0d2df4ddbe5b49610903fe837c38 DolphyWind <yunusaydin590@gmail.com> 1717850809 +1200	commit: Make crate a library
3a0b611d1f4f0d2df4ddbe5b49610903fe837c38 3292ed208adf90aafeb3fa06be96de5db267543c DolphyWind <yunusaydin590@gmail.com> 1717850835 +1200	commit: Document ArrayGrid::new
3292ed208adf90aafeb3fa06be96de5db267543c 6e717d1ecacdf7d65dc8286b4bc2bae84196d937 DolphyWind <yunusaydin590@gmail.com> 1717851523 +1200	commit: Publicise and document
6e717d1ecacdf7d65dc8286b4bc2bae84196d937 02ab80302b798f7c77be67a7b48a80d9d0b6d272 DolphyWind <yunusaydin590@gmail.com> 1718303503 +1200	commit: Remove ArrayGrid
02ab80302b798f7c77be67a7b48a80d9d0b6d272 604d2bebbcb2d028c3db5557084c357f1962300a DolphyWind <yunusaydin590@gmail.com> 1718303515 +1200	commit: Implement algorithm
604d2bebbcb2d028c3db5557084c357f1962300a de69d5f9c9859b90411801ffe38421857794a7b3 DolphyWind <yunusaydin590@gmail.com> 1718306653 +1200	commit: Formatting
de69d5f9c9859b90411801ffe38421857794a7b3 08bffdc0431a3fffb8a0ddf25eaa3a0c9149bfc6 DolphyWind <yunusaydin590@gmail.com> 1718314280 +1200	commit: Fix vision cone when against a wall
08bffdc0431a3fffb8a0ddf25eaa3a0c9149bfc6 ada0c1a90f774c2f8f96da45b6542684ae1cef1a DolphyWind <yunusaydin590@gmail.com> 1718314441 +1200	commit: Remove lenience for targeting
ada0c1a90f774c2f8f96da45b6542684ae1cef1a d6f2e25da1a935b7416862e8728892e93d341f21 DolphyWind <yunusaydin590@gmail.com> 1718314456 +1200	commit: Add demonstration program
d6f2e25da1a935b7416862e8728892e93d341f21 51779086d648aacc65219179751b778d9326c7d7 DolphyWind <yunusaydin590@gmail.com> 1718314468 +1200	commit (amend): Add demonstration program
51779086d648aacc65219179751b778d9326c7d7 e5f9ff76148428dc88fb7c82c4d14052f406a1c7 DolphyWind <yunusaydin590@gmail.com> 1718315679 +1200	commit: Add README and model image
e5f9ff76148428dc88fb7c82c4d14052f406a1c7 c7729e0e8c5b5ee524c29940e66ee08a591c7f9b DolphyWind <yunusaydin590@gmail.com> 1718315891 +1200	commit: Formatting
c7729e0e8c5b5ee524c29940e66ee08a591c7f9b 876b2b9fa19a6042539487f068bd3a440dac56df DolphyWind <yunusaydin590@gmail.com> 1718332638 +1200	commit: Simplify handling of origin square vision
876b2b9fa19a6042539487f068bd3a440dac56df 99129412b67ec819c21baad5a51dfcf02becd31e DolphyWind <yunusaydin590@gmail.com> 1718333532 +1200	commit: Fix ability to see slightly through diagonals
dir objects
dir 01
2fe745d58fa5f8c7c5a30cebfc99715d9e784f zlib compressed data
1
cg: couldn't decode file contents
dir 02
ab80302b798f7c77be67a7b48a80d9d0b6d272 zlib compressed data
1
cg: couldn't decode file contents
dir 03
ad6ebc8688e96c20bfa7da4eb741516018be69 zlib compressed data
1
cg: couldn't decode file contents
dir 08
bffdc0431a3fffb8a0ddf25eaa3a0c9149bfc6 zlib compressed data
1
cg: couldn't decode file contents
dir 12
087f106f3fa951e3a65e3f360578848f34448f zlib compressed data
1
cg: couldn't decode file contents
dir 14
65c359bbef8b29d5f806537d02335d60047a5b zlib compressed data
1
cg: couldn't decode file contents
dir 1a
66285709b4077981870eb44aaf3c09a844b400 zlib compressed data
1
cg: couldn't decode file contents
dir 24
3869209e94ae184b94f3e1cdb509e0cbd413e2 zlib compressed data
1
cg: couldn't decode file contents
dir 27
5d4d78a3076a17294f1e6c184852e130bbdf40 zlib compressed data
1
cg: couldn't decode file contents
dir 29
96d22d8599507c194c8dd5ab67fb92974c84c2 zlib compressed data
1
cg: couldn't decode file contents
dir 30
3765e0760a85661609553c672c56118617cb08 zlib compressed data
1
cg: couldn't decode file contents
5157a396c6858705a9cb625bab219053264ee4 zlib compressed data
1
cg: couldn't decode file contents
dir 32
92ed208adf90aafeb3fa06be96de5db267543c zlib compressed data
1
cg: couldn't decode file contents
dir 33
568832d5f28717afe278fbbcbf91b1e467b0a6 zlib compressed data
1
cg: couldn't decode file contents
dir 35
588af1fe35306264b7564e01d0ac6be8830c53 zlib compressed data
1
cg: couldn't decode file contents
dir 3a
0b611d1f4f0d2df4ddbe5b49610903fe837c38 zlib compressed data
1
cg: couldn't decode file contents
dir 42
0d7e28f549eec7b568c894b3938e656ff8617a zlib compressed data
1
cg: couldn't decode file contents
dir 44
5d84a31f5a15fc1dcb5eb3485d2cb12e92a1d7 zlib compressed data
1
cg: couldn't decode file contents
dir 50
0f0206f30afbe052d452a79aefac6b8437e2be zlib compressed data
1
cg: couldn't decode file contents
dir 51
779086d648aacc65219179751b778d9326c7d7 zlib compressed data
1
cg: couldn't decode file contents
dir 52
652fb92bf07daad0a35ee8c447e102ced75ff7 zlib compressed data
1
cg: couldn't decode file contents
dir 55
bf273a2907757a7d72b97ae244c08e7e4ef48d zlib compressed data
1
cg: couldn't decode file contents
dir 5c
32c6d7a19d3e8af1ca15ed73747646c1f1b17f zlib compressed data
1
cg: couldn't decode file contents
dir 5d
6ea8f7abf881bccdb29deb42d96fe33d5edab8 zlib compressed data
1
cg: couldn't decode file contents
dir 60
4d2bebbcb2d028c3db5557084c357f1962300a zlib compressed data
1
cg: couldn't decode file contents
dir 64
713ade624b6993d2e81eb4e90851c9dc8cc75d zlib compressed data
1
cg: couldn't decode file contents
dir 66
59d371bb47f4afe82a997f7de09aef78d6089e zlib compressed data
1
cg: couldn't decode file contents
dir 68
e56d3ce5e2de5318338662712c650743e0033d zlib compressed data
1
cg: couldn't decode file contents
dir 6e
717d1ecacdf7d65dc8286b4bc2bae84196d937 zlib compressed data
1
cg: couldn't decode file contents
dir 73
1509c68c046ecfa4df853c63317046795f26ca zlib compressed data
1
cg: couldn't decode file contents
dir 78
ee5262b1662f33994c56b353e73f09ab482dd1 zlib compressed data
1
cg: couldn't decode file contents
dir 7c
ae1fbcf3e390cfb5a4408b31948101331b6f47 zlib compressed data
1
cg: couldn't decode file contents
dir 7d
40bcefc2ae308fcf9134f4ec94bfc259bdf65f zlib compressed data
1
cg: couldn't decode file contents
dd12423e443215c76b5bc20ee8e92507423cb1 zlib compressed data
1
cg: couldn't decode file contents
dir 7e
96943d88b06edc986485ebd77819181725eed0 zlib compressed data
1
cg: couldn't decode file contents
dir 80
6dd7ede7fbebe3cf3e300b1b65ea9ce1e0dc3b zlib compressed data
1
cg: couldn't decode file contents
dir 81
f0b18c0a088e3ed508e2c1e91d5d39b3cb0eea zlib compressed data
1
cg: couldn't decode file contents
dir 85
25b85ce90200a913cb23a0e0a4dbb6a3c74387 zlib compressed data
1
cg: couldn't decode file contents
dir 86
b60e0168f16be602d9507c820ecfb57b699c80 zlib compressed data
1
cg: couldn't decode file contents
dir 87
6b2b9fa19a6042539487f068bd3a440dac56df zlib compressed data
1
cg: couldn't decode file contents
dir 88
80b259a960b2937691140dd3b9985d598bbc76 zlib compressed data
1
cg: couldn't decode file contents
dir 8e
e9c916c23e096d0db11962f079900b414126a8 zlib compressed data
1
cg: couldn't decode file contents
dir 90
cf223f8b77e5849a96a6c6a094ab526b1740bf zlib compressed data
1
cg: couldn't decode file contents
dir 96
ef6c0b944e24fc22f51f18136cd62ffd5b0b8f zlib compressed data
1
cg: couldn't decode file contents
dir 99
000fea5a801de01941176fad53d563eae213f5 zlib compressed data
1
cg: couldn't decode file contents
129412b67ec819c21baad5a51dfcf02becd31e zlib compressed data
1
cg: couldn't decode file contents
dir 9c
30ca322c63a9c1ac943020390099d6ec50de5c zlib compressed data
1
cg: couldn't decode file contents
dir 9e
62aaad2aa7e65f182ac6b69641985bc04924ed zlib compressed data
1
cg: couldn't decode file contents
72dfe706643142f5e285c066a4e454aa800ef2 zlib compressed data
1
cg: couldn't decode file contents
dir a0
05c1e0741ace9613f10add316670f3a9534af2 zlib compressed data
1
cg: couldn't decode file contents
dir a4
02001d1eac62cd3b6b586eb48fe0e75f3593bd zlib compressed data
1
cg: couldn't decode file contents
dir ad
a0c1a90f774c2f8f96da45b6542684ae1cef1a zlib compressed data
1
cg: couldn't decode file contents
dir b0
f597c617282abc293a948eb8ea62df14d7cf30 zlib compressed data
1
cg: couldn't decode file contents
dir b1
93587fc2b7b0882d31f4b526e714b08a5006de zlib compressed data
1
cg: couldn't decode file contents
dir b6
bde3241a7bff463d13484544943552c18cbdbd zlib compressed data
1
cg: couldn't decode file contents
dir be
696def459a45122135536e906d647d86772026 zlib compressed data
1
cg: couldn't decode file contents
dir c0
ffd6099ef1e5bae55d843891b3daae95deb6c2 zlib compressed data
1
cg: couldn't decode file contents
dir c7
729e0e8c5b5ee524c29940e66ee08a591c7f9b zlib compressed data
1
cg: couldn't decode file contents
dir d6
f2e25da1a935b7416862e8728892e93d341f21 zlib compressed data
1
cg: couldn't decode file contents
dir d7
c0e1d11538114fd1214cce499d16f2fc18d354 zlib compressed data
1
cg: couldn't decode file contents
dir de
69d5f9c9859b90411801ffe38421857794a7b3 zlib compressed data
1
cg: couldn't decode file contents
dir e1
685565cd45663d44b27c65bad8e8d1e466feec zlib compressed data
1
cg: couldn't decode file contents
dir e2
1441333aa6124abfc4b37f534b6fa5cff37e07 zlib compressed data
1
cg: couldn't decode file contents
dir e5
f9ff76148428dc88fb7c82c4d14052f406a1c7 zlib compressed data
1
cg: couldn't decode file contents
dir e7
a11a969c037e00a796aafeff6258501ec15e9a zlib compressed data
1
cg: couldn't decode file contents
dir ec
055c3bb920eeeedccfc3296fadce79c2a61406 zlib compressed data
1
码䯊쥏到㑠죍佑䠯쩌뇦Ȁ㕪լ
08560d7403934d30ee56a4a1cb72084dcdc66e zlib compressed data
1
cg: couldn't decode file contents
d909f554fe8e4a77337fe8eb1d316e9a321ede zlib compressed data
1
cg: couldn't decode file contents
dir ed
b93443dd40b2ac4c24f240955c0a5165a0004e zlib compressed data
1
cg: couldn't decode file contents
dir ee
3755f28b4f4e9352815ae4b94aa2c88fd9acea zlib compressed data
1
cg: couldn't decode file contents
dir f2
6c0179bc84d8b0f49b8355084e7474e9e4792e zlib compressed data
1
cg: couldn't decode file contents
dir f3
1d225c150873f70847a1042fd7486a59602888 zlib compressed data
1
cg: couldn't decode file contents
dir f4
24aaf940684282c7ffc64c02e769e7edc37bad zlib compressed data
1
cg: couldn't decode file contents
dir f7
a1b46d277d4650c443e92631f5e09ed5c21b10 zlib compressed data
1
码䯊쥏到㑡⠨䵒죍佑䠯쩌뇦Ȁ倷ۗ
dir fa
1ce19e75fffba85896c90c85c1db594b75a645 zlib compressed data
1
cg: couldn't decode file contents
dir refs
dir heads
master ASCII text
1
99129412b67ec819c21baad5a51dfcf02becd31e
COMMIT_EDITMSG ASCII text
1
Fix ability to see slightly through diagonals
HEAD ASCII text
1
ref: refs/heads/master
config ASCII text
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
[core]
	repositoryformatversion = 0
	filemode = true
	bare = false
	logallrefupdates = true
[user]
	name = DolphyWind
	email = yunusaydin590@gmail.com
[commit]
	gpgsign = false
description ASCII text
1
Unnamed repository; edit this file 'description' to name the repository.
index Git index, version 2, 8 entries
1
cg: couldn't decode file contents
dir examples
demo.rs ASCII text
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
use floss::{mark_vision, Grid, Pos};
use std::io::{stdin, stdout, Write};
use termion::{
    color,
    cursor::{self, HideCursor},
    event::{Event, Key, MouseButton, MouseEvent},
    input::{MouseTerminal, TermRead},
    raw::IntoRawMode,
    screen::IntoAlternateScreen,
    style,
};

pub struct Field {
    walls: Vec<bool>,
    visible: Vec<bool>,
    width: usize,
    height: usize,
    x: usize,
    y: usize,
}

impl Grid for Field {
    fn is_wall(&self, p: Pos) -> bool {
        if let Some(i) = self.pos_index(p) {
            self.walls[i]
        } else {
            true
        }
    }

    fn mark_visible(&mut self, p: Pos) {
        if let Some(i) = self.pos_index(p) {
            self.visible[i] = true;
        }
    }
}

impl Field {
    fn new((width, height): (u16, u16)) -> Self {
        let width = width as usize / 2;
        let height = height as usize;
        let size = width * height;
        Self {
            walls: vec![false; size],
            visible: vec![false; size],
            width,
            height,
            x: width / 2,
            y: height / 2,
        }
    }

    fn pos_index(&self, (x, y): Pos) -> Option<usize> {
        if x < 0 || y < 0 {
            return None;
        }
        let x = x as usize;
        let y = y as usize;
        if x < self.width && y < self.height {
            Some(y * self.width + x)
        } else {
            None
        }
    }

    fn mouse_pos_index(&self, x: u16, y: u16) -> Option<usize> {
        self.pos_index(((x as isize - 1) / 2, y as isize - 1))
    }

    fn draw(&self) {
        let player = self.y * self.width + self.x;

        print!(
            "{}{}",
            color::Bg(color::AnsiValue::grayscale(1)),
            cursor::Goto(1, 1)
        );
        for i in 0..self.walls.len() {
            if self.visible[i] {
                print!("{}{}", color::Fg(color::LightWhite), style::Bold);
            } else {
                print!(
                    "{}{}",
                    color::Fg(color::AnsiValue::grayscale(2)),
                    style::NoBold
                );
            }

            if i == player {
                print!("@ ");
            } else if self.walls[i] {
                print!("# ");
            } else {
                print!(". ");
            }

            if (i + 1) % self.width == 0 && i < self.walls.len() - 1 {
                println!("\r");
            }
        }

        stdout().flush().unwrap();
    }

    fn render(&mut self) {
        for x in self.visible.iter_mut() {
            *x = false;
        }
        mark_vision(self, (self.x as isize, self.y as isize));
        self.draw();
    }

    fn shift(&mut self, x: isize, y: isize) {
        let new_x = self.x as isize + x;
        let new_y = self.y as isize + y;
        if !self.is_wall((new_x, new_y)) {
            self.x = new_x as usize;
            self.y = new_y as usize;
        }
    }
}

fn main() {
    let _screen = HideCursor::from(MouseTerminal::from(
        stdout()
            .into_raw_mode()
            .unwrap()
            .into_alternate_screen()
            .unwrap(),
    ));

    let mut field = Field::new(termion::terminal_size().unwrap());
    field.render();

    let mut mouse_setting = None;

    for event in stdin().events() {
        match event.unwrap() {
            Event::Key(Key::Ctrl('c')) => break,
            Event::Key(Key::Esc) => break,
            Event::Key(Key::Char('w')) => field.shift(0, -1),
            Event::Key(Key::Char('a')) => field.shift(-1, 0),
            Event::Key(Key::Char('s')) => field.shift(0, 1),
            Event::Key(Key::Char('d')) => field.shift(1, 0),
            Event::Mouse(MouseEvent::Press(MouseButton::Left, x, y)) => {
                let Some(i) = field.mouse_pos_index(x, y) else {
                    continue;
                };
                let new = !field.walls[i];
                mouse_setting = Some(new);
                field.walls[i] = new;
            }
            Event::Mouse(MouseEvent::Release(_, _)) => mouse_setting = None,
            Event::Mouse(MouseEvent::Hold(x, y)) => {
                let Some(i) = field.mouse_pos_index(x, y) else {
                    continue;
                };
                let Some(x) = mouse_setting else { continue };
                field.walls[i] = x;
            }
            _ => {}
        }
        field.render();
    }
}
dir src
grid.rs ASCII text
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
/// The type of an absolute point on a grid.
/// The actual origin point of these coordinates is irrelevant as long as the [`Grid`] instance treats them
/// consistently as Cartesian coordinates. For this reason, [`isize`] is used over [`usize`] for convenience
/// in the case that your use case involves negative coordinates.
pub type Pos = (isize, isize);

/// Inspection of the contents of a grid for the purposes of vision calculation.
/// Types implementing `Grid` represent a Cartesian grid of cells, each of which either blocks
/// vision ("is a wall") or does not.
pub trait Grid {
    /// Returns whether the provided position should be considered opaque.
    /// This method should return `true` for positions that are out of bounds to prevent infinite loops.
    fn is_wall(&self, pos: Pos) -> bool;

    /// This method is called when a certain position has been found to be reachable.
    /// Note that the library does not keep track of the size of the grid, and thus
    /// you may have to do bounds checking to explicitly ignore out-of-bounds positions.
    fn mark_visible(&mut self, pos: Pos);
}
lib.rs ASCII text
1
2
3
4
5
mod grid;
mod vision;

pub use grid::{Grid, Pos};
pub use vision::mark_vision;
vision.rs ASCII text
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
use crate::grid::{Grid, Pos};

// This algorithm is easier to implement when done in octants, and the steps are exactly the same for each octant.
// This struct translates coordinates so that the code for one octant can be used on all of them.
struct Octant<'a, T> {
    grid: &'a mut T,
    origin: Pos,
    transform: (isize, isize, isize, isize),
}

impl<'a, T> Octant<'a, T> {
    fn new(grid: &'a mut T, origin: Pos) -> Self {
        Self {
            grid,
            origin,
            transform: (1, 0, 0, 1),
        }
    }

    /// Rotate the internal transform by 90 degrees.
    fn rot(&mut self) {
        let (a, b, c, d) = self.transform;
        self.transform = (-c, -d, a, b);
    }

    /// Flip the internal transform horizontally.
    fn flip(&mut self) {
        let (a, b, c, d) = self.transform;
        self.transform = (-a, -b, c, d);
    }

    fn transform(&self, x: isize, y: isize) -> Pos {
        let (a, b, c, d) = self.transform;

        (self.origin.0 + a * x + b * y, self.origin.1 + c * x + d * y)
    }
}

impl<T: Grid> Octant<'_, T> {
    fn is_wall(&self, x: isize, y: isize) -> bool {
        self.grid.is_wall(self.transform(x, y))
    }

    fn mark_visible(&mut self, x: isize, y: isize) {
        self.grid.mark_visible(self.transform(x, y))
    }
}

// x ------>
//
// -------------- <- top (slope)
// \
//  \                       y
//   \                      |
//    \                     |
//     \                    |
//      \                   v
//       \
//        \
//         \
//          \
//           \
//            \
//             \
//              \ <- bottom (slope)

// upper_scope and lower_slope find the upper and lower bounds of slope required to "hit"
// a given square, taking into account bevelling.

//       possible upper slopes
//           /---------\
//           v         v
// /---------^---------\
// |        / \        |
// |       /   \       |
// |      /     \      |
// |     /       \     |
// |    /         \    |
// |   /           \   |
// |  /             \  |
// | /               \ |
// |/                 \|
// |\                 /|
// | \               / |
// |  \             /  |
// |   \           /   |
// |    \         /    |
// |     \       /     |
// |      \     /      |
// |       \   /       |
// |        \ /        |
// \---------v---------/
fn upper_slope<T: Grid>(oct: &Octant<T>, x: isize, y: isize) -> f64 {
    let mut a = x as f64;
    let b = y as f64 - 0.5;
    if oct.is_wall(x + 1, y) || oct.is_wall(x, y - 1) || oct.is_wall(x + 1, y - 1) {
        a += 0.5;
    }
    b / a
}

//     /---------^---------\
//     |        / \        |
//     |       /   \       |
//     |      /     \      |
//     |     /       \     |
//     |    /         \    |
//     |   /           \   |
//     |  /             \  |
//     | /               \ |
//     |/                 \|
//     |\                 /|
//     | \               / |
//     |  \             /  |
//     |   \           /   |
//     |    \         /    |
//     |     \       /     |
//     |      \     /      |
//     |       \   /       |
//     |        \ /        |
//     \---------v---------/
//     ^         ^
//     \---------/
// possible lower slopes
fn lower_slope<T: Grid>(oct: &Octant<T>, x: isize, y: isize) -> f64 {
    let mut a = x as f64;
    let b = y as f64 + 0.5;
    if oct.is_wall(x - 1, y) || oct.is_wall(x, y + 1) || oct.is_wall(x - 1, y + 1) {
        a -= 0.5;
    }
    b / a
}

fn hitting<T: Grid>(oct: &Octant<T>, x: isize, y: isize, slope: f64) -> bool {
    oct.is_wall(x, y) && upper_slope(oct, x, y) < slope && slope < lower_slope(oct, x, y)
}

fn cast<T: Grid>(oct: &mut Octant<T>, x: isize, top: f64, bottom: f64) {
    let mut start = (top * x as f64).ceil() as isize;
    let mut end = (bottom * x as f64).floor() as isize;

    if hitting(oct, x, start - 1, top) {
        start -= 1;
    }
    if hitting(oct, x, end + 1, bottom) {
        end += 1;
    }

    let mut next_top = top;
    let mut last_was_wall = oct.is_wall(x, start);
    oct.mark_visible(x, start);

    for y in start + 1..=end {
        let this_is_wall = oct.is_wall(x, y);

        if last_was_wall && !this_is_wall {
            next_top = lower_slope(oct, x, y - 1);
        } else if !last_was_wall && this_is_wall {
            cast(oct, x + 1, next_top, upper_slope(oct, x, y));
        }

        if next_top == bottom {
            break;
        }
        oct.mark_visible(x, y);

        last_was_wall = this_is_wall;
    }

    if !last_was_wall {
        cast(oct, x + 1, next_top, bottom);
    }
}

/// Mark the visible points on a grid.
///
/// Calls [`Grid::mark_visible`] for each cell in `grid` visible from `origin`, including walls, non-walls, and the origin itself.
pub fn mark_vision<T: Grid>(grid: &mut T, origin: Pos) {
    grid.mark_visible(origin);
    let mut oct = Octant::new(grid, origin);
    for _ in 0..2 {
        for _ in 0..4 {
            cast(&mut oct, 1, 0.0, 1.0);
            oct.rot();
        }
        oct.flip();
    }
}
.gitignore ASCII text
1
2
/target
Cargo.lock
Cargo.toml ASCII text
1
2
3
4
5
6
7
[package]
name = "floss"
version = "0.1.0"
edition = "2021"

[dev-dependencies]
termion = "4.0.2"
README.md ASCII text
1
2
3
4
5
6
7
8
9
# floss, the LOS engine in Rust

floss implements blazingly fast, precise and beautiful line-of-sight tracking on a grid in Rust, ideal for traditional roguelikes and the like.
The library is fully documented, so you can jump right in!

For an example of what this library does, run the example (`cargo run --release --example demo`). Use WASD to move around and left click to place or remove walls.
`Esc` or `Ctrl-C` quits.

Please note that the library supports no customization of the algorithm of any kind. What you get is what you get.
model.png PNG image data, 600 x 320, 8-bit/color RGBA, non-interlaced