LINUX.ORG.RU

Написание простой SDL-программы на Pascal

 , , , ,


2

3

В топике про новое издание учебника программирования от Столярова появился @bloody_enterprise и сказал, что ему сложно было осваивать паскаль по книге «Паскаль для школьников» Д. М. Ушакова.

Среди заданий там было

Задание 5.7. Ввести значение X и, используя график функции, определить значение Y. Требуется заполнить блок-схему алгоритма.

График функции рисовать не требовалось, но мне захотелось это сделать. Далее рассказывается, как именно я решал эту задачу. Приведен полный исходный код трёх программ, написанных мной, а также их скриншоты. Разрешается включение кода в проекты под любой свободной лицензией без необходимости упоминания автора. Если кому-то эта статья или программы пригодились, мне было бы интересно об этом узнать.

Первый блин комом:

Free Pascal Compiler version 3.2.2 [2021/06/29] for x86_64
Copyright (c) 1993-2021 by Florian Klaempfl and others
Target OS: Linux for x86-64
Compiling ./ushakov5.7-piece-function-graph.pas
ushakov5.7-piece-function-graph.pas(2,12) Fatal: Can't find unit graph used by piecewise_function
Fatal: Compilation aborted
Error: /usr/bin/ppcx64 returned an error exitcode

Похоже, что модуль graph по умолчанию не включается в пакет с free pascal, хотя где-то его можно найти. Пересобирать fpc я не стал, а начал искать альтернативу. Всё что мне нужно — это доступ к прямоугольнику с пикселями.

Во-первых, посмотрим в каталог с модулями:

$ ls /usr/lib64/fpc/3.2.2/units/x86_64-linux/graph/*.ppu
/usr/lib64/fpc/3.2.2/units/x86_64-linux/graph/ggigraph.ppu  /usr/lib64/fpc/3.2.2/units/x86_64-linux/graph/ptcgraph.ppu
/usr/lib64/fpc/3.2.2/units/x86_64-linux/graph/ptccrt.ppu    /usr/lib64/fpc/3.2.2/units/x86_64-linux/graph/ptcmouse.ppu

Что такое ggigraph и ptcgraph, я не знал, но попытка взять код для обычного graph и просто поменять строку в Uses сходу не прошла. Хотя позже я разобрался и ptcgraph всё-таки заработал с кодом из сообщения на freepascal.org.

Поэтому я стал искать дальше:

$ (cd /usr/lib64/fpc/3.2.2/units/x86_64-linux/x11/; ls *.ppu)
cursorfont.ppu  keysym.ppu     xcms.ppu        xfixes.ppu  xi2.ppu       xkb.ppu     xrender.ppu    xv.ppu
deckeysym.ppu   sunkeysym.ppu  xf86dga.ppu     xft.ppu     xinerama.ppu  xkblib.ppu  xresource.ppu  xvlib.ppu
fontconfig.ppu  x.ppu          xf86keysym.ppu  xge.ppu     xinput.ppu    xlib.ppu    xshm.ppu
hpkeysym.ppu    xatom.ppu      xf86vmode.ppu   xi.ppu      xinput2.ppu   xrandr.ppu  xutil.ppu

Как видим, тут много всего, жаль только нет xcb, говорят, xlib очень сложная. И тут мне напомнили про существование SDL. И верно, она тоже прилагается к компилятору:

$ (cd /usr/lib64/fpc/3.2.2/units/x86_64-linux/sdl/; ls *.ppu)
logger.ppu  sdl_gfx.ppu    sdl_mixer.ppu          sdl_net.ppu  smpeg.ppu
sdl.ppu     sdl_image.ppu  sdl_mixer_nosmpeg.ppu  sdl_ttf.ppu

Внятной информации, что с этим делать сходу найти не получилось. В wiki freepascal всё очень пусто, только в секции Examples нашелся один пример и в отличие от всего остального, оно даже мигнуло графическим окошком!

Дальше я начал читать Tutorial и там было написано, что в программе обязательно должен быть хотя бы один Window и Renderer. Но в примере-то был только Surface и всё! Странно конечно. Только позже я понял, что туториал по SDL2, а пример по SDL1.x но дальше дело не шло, окошко-то висело, но попытки рисовать путём записи байтов кончались ничем.

Тут пришлось обратиться к документации по подсказке нейронки, хотя я и так догадывался, что нужна какая-то процедура, чтобы нарисованное отобразилось на экране:

SDL_UpdateRect(screen, 0, 0, 0, 0);

То есть, минимальный рабочий пример — это написать что-нибудь по указателю pixels, к которому можно обращаться как к array of bytes и добавить вызов процедуры, чтобы это фактически отобразилось. Для теста мне нужно было что-нибудь, чтобы определить рисуются пиксели или нет, и в голову первой пришла идея с формулой: pixel^ := i * j mod 256;

Получилось неожиданно красиво (см. первый скриншот), но код я не сохранил и продолжил редактирование. Теперь, для статьи я написал его заново, попытавшись сделать его минимальным, но при этом достаточным для понимания сути. Получилось 64 строки. Кто учится писать на паскале с графикой, пишите свои отзывы, где не хватает комментариев, где сходу непонятно и тд.

Program xymod256sdl;
Uses sdl;

Procedure draw(surface: PSDL_Surface);
var
	pixel:  ^byte;
	width:	integer;
	height: integer;
	pitch:  integer;
	x, y:   integer;
begin
	pixel  := surface^.pixels;
	pitch  := surface^.pitch;
	width  := surface^.w;
	height := surface^.h;

	for y := 0 to height - 1 do begin
		for x := 0 to width - 1 do begin
			pixel[y*pitch+x] := x * y mod 256
		end
	end;
	SDL_UpdateRect(surface, 0, 0, 0, 0)
end;

Const
	default_width  = 768;
	default_height = 512;
Var
	status:        longint; {actual return type is cint}
	surface:       PSDL_Surface;
	Event:         TSDL_Event;
	ExitEventLoop: boolean = false;
Begin
	status := SDL_Init(SDL_INIT_VIDEO);
	if status < 0 then begin
		writeln(ErrOutput, 'Fatal: cannot initialize SDL: ', status);
		halt(1)
	end;

	surface := SDL_SetVideoMode(default_width, default_height, 8, SDL_SWSURFACE);
	if surface = nil then begin
		writeln(ErrOutput, 'Fatal: cannot create SDL surface');
		halt(2)
	end;

	SDL_WM_SetCaption('x * y mod 256', 'No Icon');

	draw(surface); {we might need to redraw later, therefore procedure}
	
	repeat 
		status := SDL_WaitEvent(@Event);
		if status = 0 then begin
			writeln(ErrOutput, 'Debug: event status: ', status);
			continue
		end;
		if Event.type_ = SDL_QUITEV then
			ExitEventLoop := true;
		if (Event.type_ = SDL_KEYUP) and (Event.key.keysym.sym = SDLK_ESCAPE) then
			ExitEventLoop := true
	until ExitEventLoop;

	SDL_FreeSurface(surface);
	SDL_Quit
End.

В переписанной программе я учёл нюансы, например вместо SDL_PollEvent взял SDL_WaitEvent, поскольку первая функция асинхронная, то есть с ней программа поедает 100% процессора. А SDL_WaitEvent блокирует процесс до появления событий.

Но теперь нужно как-то нарисовать сам график. Для начала я писал константы в верхнюю секцию Const, а рисование графика засунул прямо в главную процедуру между Begin и End.

Const
	width       = 800;
	height      = 600;
	origin_j    = width  div 2;
	origin_i    = height div 2;
	axis_color  = 0;    {black}
	graph_color = 0;    {black}
	bg_color    = 255;  {white}
	px_per_unit = 100;
	epsilon     = 0.01; {line width}
	bs          = 11;   {bitmap size}
...

Procedure scale(i, j: integer; var x, y: real);
begin
	x := (j - origin_j) / px_per_unit;
	y := (origin_i - i) / px_per_unit;
end;

...
	for i:=0 to height-1 do begin
		for j:=0 to width-1 do begin
			pixel  := screen^.pixels+i*width+j; {Mistake! width should be pitch}
			pixel^ := bg_color;
			if (i = origin_i) or (j = origin_j) then
				pixel^ := axis_color;
			scale(i, j, x, y);
			if abs(y - f(x)) < epsilon then
				pixel^ := graph_color
		end
	end;

Но такой подход оказался не совсем верным. Во-первых, мне повезло с одним байтом на пиксель и чётным размером окна и программа рисовала правильно, но на самом деле для индексации строчек нужно было брать не width, а pitch, где width — это ширина картинки в пикселях, а pitch — размер строчки картинки в байтах. Это важное отличие, поскольку в каждой строке может быть несколько неиспользуемых байтов для выравнивания. Для решения этой проблемы нужно было просто заменить width на pitch в индексации массива, но оставить width в параметре цикла for, что я и сделал.

Во-вторых, формула abs(y - f(x)) < epsilon, где epsilon константа, имеет проблему, в этом случае горизонтальная линия получается толстой, а вертикальная ветка параболы утончается. Это выглядит забавно, но это не то, что требуется.

В-третьих, мне захотелось добавить возможность двигать картинку и оказалось, что отрисовывание графика прямо в главной процедуре не позволяет перерисовать его ещё раз по событию без дублирования кода.

Для решения проблемы с толщиной графика было два варианта: первый вариант более быстрый и который обычно и используют — применить Bresenham’s line algorithm или его упрощённую версию — заполнять вертикальную строку пикселей между последовательными точками поделив колонки пополам или пропорционально. Для этого нужно преобразовывать координаты из математических в экранные. P.S. Или ещё более простой вариант — вычислять значения функции на границах пикселей и заполнять вертикальную колонку.

Я выбрал второй вариант — изменить, то что у меня уже было, где я преобразовывал экранные координаты в математические, просто меняя epsilon в зависимости от первой производной функции. Чем быстрее растёт функция, тем при большем вертикальном расстоянии до математического графика функции пиксель должен быть заполнен. При этом применяется формула ε=ε₀·√((y')²+1), основанная на теореме Пифагора. Для этого пришлось ввести функцию df(x) которая представляет собой производную f(x).

А вот в третьем случае оказалось, что нужно было вынести логику отрисовки в отдельную процедуру, чтобы её можно было дёргать как сразу после инициализации графики, так и в качестве реакции на некоторые события. Тут как раз помогли именованные константы. Как только понадобится их изменить, их можно легко переделать в переменные, хотя я их ещё и переименовал, что было уже не очень легко, но в таком коротком коде с поиском и заменой особого труда не составило.

Кроме того, мне захотелось сделать оси координат правильными — со стрелочками, рисками и подписями. Для этого я вначале использовал смешанный подход. X, Y, O (от слова Origin), 1 я нарисовал с помощью констант-массивов, а риски с помощью циклов. Но этот подход оказался неверным, когда я добавил функцию масштабирования. Если риска выходит за пределы экрана, то она вызывает Access violation и падение программы и то же самое делает и картинка. То есть пришлось бы добавлять логику проверки на выход за экран два раза. Вместо этого я просто переделал риски на осях на картинки, а затем в случае, если поступает команда нарисовать картинку вне экрана, процедура просто ничего не рисует. После этого падения программы прекратились.

Само масштабирование было выполнено путём превращения констант в переменные и изменении этих переменных в цикле событий как реакции на нажатия клавиш стрелок и +/-. При этом пришлось передавать данные в процедуры и их заголовки сильно разрослись.

В итоге получилось это:

Program piecewise_function;
Uses math, sdl;
Type
	tbitmap = array of array of byte;
Const
	axis_color  = 0;    {black}
	graph_color = 0;    {black}
	bg_color    = 255;  {white}
	bs          = 11;   {bitmap size}

	(* Produced with gimp and cat foo.pbm | tail -n +4 | tr -d \\n | \
	   fold -bw 11 | sed 's/./&,/g;s/^/\t\t(/;s/,$/),/;$s/,$/\n/' *)
	bmp_right_arrow: tbitmap = (
		(0,0,0,1,0,0,0,0,0,0,0),
		(0,0,0,0,1,1,0,0,0,0,0),
		(0,0,0,0,0,1,1,0,0,0,0),
		(0,0,0,0,0,0,1,1,0,0,0),
		(0,0,0,0,0,0,1,1,1,0,0),
		(1,1,1,1,1,1,1,1,1,1,1),
		(0,0,0,0,0,0,1,1,1,0,0),
		(0,0,0,0,0,0,1,1,0,0,0),
		(0,0,0,0,0,1,1,0,0,0,0),
		(0,0,0,0,1,1,0,0,0,0,0),
		(0,0,0,1,0,0,0,0,0,0,0)
	);
	bmp_up_arrow: tbitmap = (
		(0,0,0,0,0,1,0,0,0,0,0),
		(0,0,0,0,0,1,0,0,0,0,0),
		(0,0,0,0,1,1,1,0,0,0,0),
		(0,0,0,1,1,1,1,1,0,0,0),
		(0,0,1,1,1,1,1,1,1,0,0),
		(0,1,1,0,0,1,0,0,1,1,0),
		(0,1,0,0,0,1,0,0,0,1,0),
		(1,0,0,0,0,1,0,0,0,0,1),
		(0,0,0,0,0,1,0,0,0,0,0),
		(0,0,0,0,0,1,0,0,0,0,0),
		(0,0,0,0,0,1,0,0,0,0,0)
	);
	bmp_capital_x: tbitmap = (
		(0,0,0,0,0,0,0,0,0,0,0),
		(0,0,1,0,0,0,0,0,1,0,0),
		(0,0,0,1,0,0,0,1,0,0,0),
		(0,0,0,0,1,0,1,0,0,0,0),
		(0,0,0,0,1,0,1,0,0,0,0),
		(0,0,0,0,0,1,0,0,0,0,0),
		(0,0,0,0,1,0,1,0,0,0,0),
		(0,0,0,0,1,0,1,0,0,0,0),
		(0,0,0,1,0,0,0,1,0,0,0),
		(0,0,1,0,0,0,0,0,1,0,0),
		(0,0,0,0,0,0,0,0,0,0,0)
	);
	bmp_capital_y: tbitmap = (
		(0,0,0,0,0,0,0,0,0,0,0),
		(0,1,1,0,0,0,0,0,1,1,0),
		(0,0,1,1,0,0,0,1,1,0,0),
		(0,0,0,1,1,0,1,1,0,0,0),
		(0,0,0,0,1,1,1,0,0,0,0),
		(0,0,0,0,0,1,0,0,0,0,0),
		(0,0,0,0,0,1,0,0,0,0,0),
		(0,0,0,0,0,1,0,0,0,0,0),
		(0,0,0,0,0,1,0,0,0,0,0),
		(0,0,0,0,1,1,1,0,0,0,0),
		(0,0,0,0,0,0,0,0,0,0,0)
	);
	bmp_capital_o: tbitmap = (
		(0,0,0,0,0,0,0,0,0,0,0),
		(0,0,0,0,1,1,1,0,0,0,0),
		(0,0,0,1,0,0,0,1,0,0,0),
		(0,0,1,0,0,0,0,0,1,0,0),
		(0,0,1,0,0,0,0,0,1,0,0),
		(0,0,1,0,0,0,0,0,1,0,0),
		(0,0,1,0,0,0,0,0,1,0,0),
		(0,0,1,0,0,0,0,0,1,0,0),
		(0,0,0,1,0,0,0,1,0,0,0),
		(0,0,0,0,1,1,1,0,0,0,0),
		(0,0,0,0,0,0,0,0,0,0,0)
	);
	bmp_digit_1: tbitmap = (
		(0,0,0,0,0,0,0,0,0,0,0),
		(0,0,0,0,0,1,0,0,0,0,0),
		(0,0,0,0,1,1,0,0,0,0,0),
		(0,0,0,1,0,1,0,0,0,0,0),
		(0,0,0,0,0,1,0,0,0,0,0),
		(0,0,0,0,0,1,0,0,0,0,0),
		(0,0,0,0,0,1,0,0,0,0,0),
		(0,0,0,0,0,1,0,0,0,0,0),
		(0,0,0,0,0,1,0,0,0,0,0),
		(0,0,0,1,1,1,1,1,0,0,0),
		(0,0,0,0,0,0,0,0,0,0,0)
	);
	bmp_tick_mark: tbitmap = (
		(0,0,0,0,0,1,0,0,0,0,0),
		(0,0,0,0,0,1,0,0,0,0,0),
		(0,0,0,0,0,1,0,0,0,0,0),
		(0,0,0,0,0,1,0,0,0,0,0),
		(0,0,0,0,0,1,0,0,0,0,0),
		(1,1,1,1,1,1,1,1,1,1,1),
		(0,0,0,0,0,1,0,0,0,0,0),
		(0,0,0,0,0,1,0,0,0,0,0),
		(0,0,0,0,0,1,0,0,0,0,0),
		(0,0,0,0,0,1,0,0,0,0,0),
		(0,0,0,0,0,1,0,0,0,0,0)
	);

Function f(x: real): real;
begin
	if x <= -1 then
		f := -2
	else if x <= 2 then
		f := x - 1
	else
		f := (x - 2)**2 + 1;
end;

{Derivative of f is necessary for epsilon adjustment}
Function df(x: real): real;
begin
	if x <= -1 then
		df := 0
	else if x <= 2 then
		df := 1
	else
		df := 2 * (x - 2)
end;

Function epsilon_scale(x: real): real;
begin
	epsilon_scale := sqrt(x * x + 1)
end;

Procedure place_bitmap(
	canvas:             pbyte;
	pitch:              integer;
	canvas_w, canvas_h: integer;
	bmp:                tbitmap;
	bmp_w, bmp_h:       integer;
	x, y:               integer;
	bg_color, fg_color: byte
);
var i, j: integer;
begin
	if (x < 0) or (y < 0) or (x + bmp_w > canvas_w) or (y + bmp_h > canvas_h) then
		exit; {if user pans too far do not draw the bitmap}
	for i:=0 to bmp_h-1 do
		for j:=0 to bmp_w-1 do
			if bmp[i,j] = 1 then
				canvas[(y+i)*pitch+(x+j)] := fg_color
{			else
				canvas[(y+i)*pitch+(x+j)] := bg_color
}
end;

Procedure draw_func_g(canvas: pbyte; pitch, canvas_w, canvas_h, ox, oy, scale: integer);
var
	i, j: integer;
	x, y: real;
	epsilon: real;
	pixel: ^byte;
begin
	for i:=0 to canvas_h-1 do begin
		for j:=0 to canvas_w-1 do begin
			pixel := canvas+i*pitch+j;
			x := (j - ox) / scale;
			y := (oy - i) / scale;
			{line thickness has to be adjusted depending on derivative}
			epsilon := epsilon_scale(df(x)) / scale;
			if abs(y - f(x)) < epsilon then
				pixel^ := graph_color
			else
				pixel^ := bg_color;
		end
	end;
end;

Procedure draw_axis_x(canvas: pbyte; pitch, canvas_w, canvas_h, ox, oy, scale: integer);
var
	j: integer;
	pixel: ^byte;
begin
	if (oy < 0) or (oy > canvas_h) then
		exit; {if user pans too far, do not draw axis X}
	for j := 0 to canvas_w-1 do begin
		pixel  := canvas+oy*pitch+j;
		pixel^ := axis_color
	end;
	place_bitmap(
		canvas, pitch, canvas_w, canvas_h, bmp_right_arrow,
		bs, bs, canvas_w-bs, oy-(bs div 2), bg_color, axis_color
	);
	place_bitmap(
		canvas, pitch, canvas_w, canvas_h, bmp_capital_x,
		bs, bs, canvas_w-2*bs, oy+(bs div 2), bg_color, axis_color
	);
	place_bitmap(
		canvas, pitch, canvas_w, canvas_h, bmp_digit_1,
		bs, bs, ox+scale-(bs div 2), oy+bs, bg_color, axis_color
	);
	place_bitmap(
		canvas, pitch, canvas_w, canvas_h, bmp_tick_mark,
		bs, bs, ox+scale-(bs div 2), oy-(bs div 2), bg_color, axis_color
	)
end;

Procedure draw_axis_y(canvas: pbyte; pitch, canvas_w, canvas_h, ox, oy, scale: integer);
var
	i: integer;
	pixel: ^byte;
begin
	if (ox < 0) or (ox > canvas_w) then
		exit; {if user pans too far, do not draw axis Y}
	for i := 0 to canvas_h-1 do begin
		pixel  := canvas+i*pitch+ox;
		pixel^ := axis_color
	end;
	place_bitmap(
		canvas, pitch, canvas_w, canvas_h, bmp_up_arrow, bs, bs, 
		ox-(bs div 2), 0, bg_color, axis_color
	);
	place_bitmap(
		canvas, pitch, canvas_w, canvas_h, bmp_capital_y, bs, bs,
		ox+(bs div 2), bs, bg_color, axis_color
	);
	place_bitmap(
		canvas, pitch, canvas_w, canvas_h, bmp_digit_1, bs, bs,
		ox-(bs * 3 div 2), oy-scale-(bs div 2), bg_color, axis_color
	);
	place_bitmap(
		canvas, pitch, canvas_w, canvas_h, bmp_tick_mark, bs, bs,
		ox-(bs div 2), oy-scale-(bs div 2), bg_color, axis_color
	)
end;

Procedure draw_everything(screen: PSDL_Surface; ox, oy, scale: integer);
begin
	{it has to be first since it also updates background}
	draw_func_g(screen^.pixels, screen^.pitch, screen^.w, screen^.h, ox, oy, scale);
	draw_axis_x(screen^.pixels, screen^.pitch, screen^.w, screen^.h, ox, oy, scale);
	draw_axis_y(screen^.pixels, screen^.pitch, screen^.w, screen^.h, ox, oy, scale);
	{label O for origin}
	place_bitmap(
		screen^.pixels, screen^.pitch, screen^.w, screen^.h, bmp_capital_o, bs, bs,
		ox-(bs * 3 div 2), oy+(bs div 2), bg_color, axis_color
	);
	SDL_UpdateRect(screen, 0, 0, 0, 0);
end;

Const {constants for main procedure}
	ospeed      = 20;
	scalespeed  = 20;
Var
	status: integer;
	screen: PSDL_Surface;
	ExitEventLoop: boolean = false;
	Event: TSDL_Event;
	width:  integer = 800;
	height: integer = 600;
	ox:     integer;
	oy:     integer;
	old_width, old_height: integer;
	scale:  integer = 100;
Begin
	{SDL init}
	status := SDL_Init(SDL_INIT_VIDEO); {This might be a problem with cint}
	if status < 0 then begin
		writeln(ErrOutput, 'Fatal: cannot init SDL: ', status);
		halt(1)
	end;
	screen := SDL_SetVideoMode(width, height, 8, SDL_HWSURFACE or SDL_RESIZABLE);
	if screen = nil then begin
		writeln(ErrOutput, 'Fatal: cannot create SDL surface');
		halt(2)
	end;
	{What is icon name? Is it used anywhere}
	SDL_WM_SetCaption('Function plotter 9000', 'Icon name');
	{SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);}
	{Default was 500, 30}
	SDL_EnableKeyRepeat(100, 5);

	{Actual drawing}
	ox := width  div 2;
	oy := height div 2;
	draw_everything(screen, ox, oy, scale);

	{Event loop for SDL}
	while not ExitEventLoop do begin
		status := SDL_WaitEvent(@Event);
		if status = 0 then
			continue;

		if Event.type_ = SDL_VIDEORESIZE then begin
			old_width  := width;
			old_height := height;
			width      := Event.resize.w;
			height     := Event.resize.h;
			screen := SDL_SetVideoMode(width, height, 8, SDL_HWSURFACE or SDL_RESIZABLE);
			ox := ox * width div old_width;
			oy := oy * height div old_height;
		end;

		if Event.type_ = SDL_KEYDOWN then begin
			case Event.key.keysym.sym of
				SDLK_UP     : oy := oy - ospeed;
				SDLK_DOWN   : oy := oy + ospeed;
				SDLK_LEFT   : ox := ox - ospeed;
				SDLK_RIGHT  : ox := ox + ospeed;
				SDLK_EQUALS : scale := scale + scalespeed;
				SDLK_MINUS  : scale := scale - scalespeed;
			end;
			if scale < 1 then
				scale := 1;
		end;

		draw_everything(screen, ox, oy, scale);

		{if the event requires quit, you don't need to update the screen}
		if Event.type_ = SDL_QUITEV then
			ExitEventLoop := true;
		if (Event.type_ = SDL_KEYUP) and (Event.key.keysym.sym = SDLK_ESCAPE) then
			ExitEventLoop := true
	end;
	
	SDL_FreeSurface(screen);
	SDL_Quit
End.

Алгоритм отрисовки графика имеет сложность O(n²), хотя можно было обойтись O(n), из-за чего при открытии окна на большой размер может притормаживать. На 800x600 всё гладко. Кроме того, похоже график не успевает обработать отрисовку и иногда клавиши стрелок слегка залипают. Можно увеличить время повтора и ospeed и scalespeed, но тогда точность позиционирования понизится.

Для улучшения читаемости кода стоило бы сделать canvas структурой (record) и то же самое сделать с tbitmap — сделать структурой с размерами по ширине и высоте. Тогда у процедур будет меньше аргументов. Почти десяток — это всё-таки избыточно.

Ещё явно требуется сделать поддержку ускорения, то есть чем дольше жмёшь кнопку со стрелкой, тем быстрее двигается график, чтобы можно было установить положение с точностью до пикселя и при этом не ждать слишком долго, если нужно двинуться далеко.

Не помешает обрабатывать и события мыши, это должно быть не слишком сложно. Тогда можно таскать график по экрану и скроллом увеличивать и уменьшать, например.

Также не помешает сделать вычисление производной функции приближенно, используя саму функцию, а потом, возможно, добавить и интерпретатор выражений, чтобы можно было поменять функцию без перекомпиляции программы.

Нейронная сеть мне сделала замечание, что в событии resize нужно освобождать экран перед присваиванием, но я не заметил никакой утечки памяти, поэтому делать это не стал.

Но делать это всё (пока?) мне не хочется, да и нужно же читателю статьи на чём-то поупражняться, верно?

Вместо этого я написал новую программу, которая, на мой взгляд, интереснее чем график обычной функции:

Program bifdiag_sdl;
Uses sdl;

Procedure draw(surface: PSDL_Surface);
const
	rmin  = 0.0;
	rmax  = 4.0;
	iter:  longint  = 11000;
	lasti: longint  = 10000;
var
	pixel:   ^byte;
	curpx:   ^byte;
	width:	 integer;
	height:  integer;
	pitch:   integer;
	i, j, k: integer;
	x, r:    extended;
begin
	pixel  := surface^.pixels;
	pitch  := surface^.pitch;
	width  := surface^.w;
	height := surface^.h;

	for i := 0 to width - 1 do begin
		r := rmin + (rmax - rmin) * sqrt(sqrt(sqrt(i/width)));
		x := 0.5;
		for j := 1 to iter do begin
			x := r * x * (1 - x);
			if j > (iter - lasti) then begin
				curpx := pixel + ((height - 1) - round(x * (height - 1)))*pitch + 3*i;
				if (curpx[2] < 254) and (curpx[2] <> 1) then begin
					curpx[2] := (curpx[2] + 2)
				end else if (curpx[1] < 254) and (curpx[1] <> 1) then begin
					curpx[2] := 1;
					curpx[1] := (curpx[1] + 2)
				end else begin
					curpx[1] := 1;
					curpx[0] := (curpx[0] + 1);
				end;
				if curpx[0] >= 255 then begin
					for k := 0 to 2 do
						curpx[k] := $ff;
					{writeln('white: r=', r:0:8, ', x=', x:0:8);}
					break
				end
			end
		end;
		if i mod 16 = 0 then
			SDL_UpdateRect(surface, 0, 0, 0, 0)
	end;
	SDL_UpdateRect(surface, 0, 0, 0, 0)
end;

Const
	default_width  = 800;
	default_height = 600;
Var
	status:        longint; {actual return type is cint}
	surface:       PSDL_Surface;
	Event:         TSDL_Event;
	ExitEventLoop: boolean = false;
Begin
	status := SDL_Init(SDL_INIT_VIDEO);
	if status < 0 then begin
		writeln(ErrOutput, 'Fatal: cannot initialize SDL: ', status);
		halt(1)
	end;

	surface := SDL_SetVideoMode(default_width, default_height, 24, SDL_SWSURFACE);
	if surface = nil then begin
		writeln(ErrOutput, 'Fatal: cannot create SDL surface');
		halt(2)
	end;

	SDL_WM_SetCaption('Bifurcation diagram of the logistic map', 'No Icon');

	draw(surface); {we might need to redraw later, therefore procedure}
	
	repeat 
		status := SDL_WaitEvent(@Event);
		if status = 0 then begin
			writeln(ErrOutput, 'Debug: event status: ', status);
			continue
		end;
		if Event.type_ = SDL_QUITEV then
			ExitEventLoop := true;
		if (Event.type_ = SDL_KEYUP) and (Event.key.keysym.sym = SDLK_ESCAPE) then
			ExitEventLoop := true
	until ExitEventLoop;

	SDL_FreeSurface(surface);
	SDL_Quit
End.

Общая логика программы взята из первого примера, который является неплохим шаблоном для программ, где нужно просто что-нибудь делать с пикселями и выводить это всё на экран. Теперь разберём, как это работает.

const
	rmin  = 0.0;
	rmax  = 4.0;
	iter:  longint  = 11000;
	lasti: longint  = 10000;

Константы rmin и rmax указывают, какая часть логистического отображения нам интересна. Я в данном случае беру всё полностью, от 0 до 4. iter и lasti указывают, сколько итераций проводить для конкретного значения r и сколько из них брать, в данном случае первая тысяча итераций отбрасывается, чтобы процесс успел установиться.

r := rmin + (rmax - rmin) * sqrt(sqrt(sqrt(i/width))); — это формула пересчёта r в экранную координату. Три квадратных корня я добавил, чтобы сжать малоинтересную часть графика с одной, двумя и тд бифуркациями и детализировать r близкие к верхнему пределу 4.0. Если же r становится больше четырёх, то некоторые точки вылетают за интервал (0, 1) и происходит переполнение типа extended и программа вылетает.

x := r * x * (1 - x); — это логистическое отображение, одна из самых простых формул, которые позволяют получить бифуркции.

curpx := pixel + (height - round(x * height))*pitch + 3*i;

Поскольку я использовал 24-битный цвет на этот раз, width измеряет строку в пикселях, а pitch в байтах и готового 24-битного целого вроде нет, можно использовать два подхода. Один вариант - int32, где верхние 8 бит игнорируются. Поскольку у нас порядок байт Little Endian почти везде (x86, x86-64, arm, aarch64), следующая итерация будет перезаписывать верхний байт. Изначально я так и сделал, но затем решил поиграть с цветовыми компонентами, поэтому удобнее получилось воспринимать каждый пиксель как массив из трёх байт. Чтобы не повторять громоздкую формулу, я использую промежуточную переменную curpx.

if (curpx[2] < 254) and (curpx[2] <> 1) then begin
	curpx[2] := (curpx[2] + 2)
end else if (curpx[1] < 254) and (curpx[1] <> 1) then begin
	curpx[2] := 1;
	curpx[1] := (curpx[1] + 2)
...

А в этом коде и далее я просто перехожу по цветовым каналам, у SDL они расположены в порядке BGR. Вначале я работаю с красным каналом, если он переполняется, почти обнуляю его (ставлю в 1), используя эту единицу как маркер и работаю уже с зеленым каналом, потом с синим, и наконец, если даже синий канал переполняется, выставляю всё в белый цвет.

Как показала практика, на самом деле, в такой сложной логике толку мало, поскольку почти всё оказывается закрашено красным цветом, так что можно просто увеличивать все три цветовых канала на единицу одновременно и получить красивую серую картинку. Или только один — тогда будет синяя, зеленая или красная.

В остальном код аналогичен первому примеру. Ещё интересный квадратный фрактал получается, если заменить умножение на xor или другую логическую операцию в первом примере.

★★★★★

Проверено: hobbit ()
Последнее исправление: Xenius (всего исправлений: 23)

/* Эта статья находится в процессе разработки, пока подтверждать её не нужно. Я вообще только недавно заметил, что на ЛОРе есть какие-то статьи */

Жаль у нас такие тэги не поддерживаются, а то бы читали пост дальше этого абзаца.

IIIypuk ★★★★☆
()

Кстати, ptcgraph всё-таки работает. Надо было посмотреть на форуме freepascal.

Xenius ★★★★★
() автор топика

Эта статья находится в процессе разработки, пока подтверждать её не нужно.

Я недавно в модераторском чатике предложил добавить возможность пометки новостей/статей/скриншотов «Готово к публикации». А то часто бывает так, что два модератора/корректора что-то исправляют одновременно. Вот и в этом случае бы пригодилось.

Я вообще только недавно заметил, что на ЛОРе есть какие-то статьи

И заметь, давно есть черновики.

dataman ★★★★★
()
Ответ на: комментарий от dataman

Так код-то более-менее готов и я хотел его показать. Я просто решил что это лучше как формат статьи, а не поста в Development потому что я не прошу что-то мне помочь исправить.

Черновиком же нельзя поделиться?

Xenius ★★★★★
() автор топика
Ответ на: комментарий от Xenius

Так код-то более-менее готов и я хотел его показать.

Ok, тогда готовься к дальнейшей критике. :)

Черновиком же нельзя поделиться?

Только с модераторами. Но это удобный способ никого не беспокоить. Я вот часто использую черновики, особенно для чего-то объёмного и не быстрого.

dataman ★★★★★
()
Последнее исправление: dataman (всего исправлений: 1)
Ответ на: комментарий от dataman

Да нет, в данном случае нахождение в неподтверждённых – нормально, может, действительно автор учтёт какую-то критику до подтверждения.

Автору: я убрал основную часть текста под спойлер, в окончательном варианте это должно остаться.

hobbit ★★★★★
()

Отличное начинание!

так и видится «Экстра вигилантный решебник к учебнику X»

скажем к васильевскому по питону - и да решения как 2 moda

т.е показывающие что детские задачки детские только в узком(ставящем на лыжню) контекстве обучающего правильным манерам обучаемого.

вон на тэде был какой то сэр с телегой что образование выключает криативность - это вот оно самое

qulinxao3 ★☆
()

Тут пришлось обратиться к нейронке

Тьфу ты. Нормальные программисты обращаются к документации. Ну или полностью на вайбкоде.

peregrine ★★★★★
()
Ответ на: комментарий от peregrine

Так я прочитал man SDL_UpdateRect, но вначале мне нужно было как-то узнать, что мне нужно именно это.

А код от нейронки запускать напрямую - не самое полезное занятие. Вдруг она тебе подсунет rm –rf замаскированный?

К тому же она пишет какую-то ерунду обычно, но полезные идеи проскакивают.

Xenius ★★★★★
() автор топика
Последнее исправление: Xenius (всего исправлений: 4)

доступ к прямоугольнику с пикселями

Тьфу ещё раз. Доступ к прямоугольнику с пикселями - самая жопа во всех ЯП, т.к. тот доступ который был во времена царя гороха на первых машинах давно уже пропал (да и настоящим если и был, то очень давно, потом его долго-долго эмулировали), сейчас это всё делается через кучу абстракций и прослоек, даже если кажется что это не так, потому что попиксельное рисование находится внутри видеодрайверов и видеокарты, а там всё огорожено от сторонних ручек. Самое быстрое и низкоуровневое рисование через OpenGL/vulcan и то рисует текстурку (даже шейдеры всего-лишь переложат нагрузку с CPU на GPU), которая потом скармливается видеокарте и там под капотом опять будет куча возни.

peregrine ★★★★★
()
Последнее исправление: peregrine (всего исправлений: 2)
Ответ на: комментарий от Xenius

Я немного возился с SDL2, когда он только появился и обычно просто бегло смотрю всю документацию (ИМХО в случае SDL надо предварительно потратить пару дней для того, чтоб лучше понять её, а после уже подробнее тыкать в нужные функции), а потом подробнее то что надо. А так ИИ-шница тоже гланды через попу удалять советует порой. Т.е. у фреймворка есть правильный способ как что-то сделать и костыль. Вот ИИ часто костыль предлагает, зачастую даже не рабочий или с побочными эффектами. А зачем ты вообще SDL2 взял, когда уже актуален SDL3?

peregrine ★★★★★
()
Ответ на: комментарий от etwrq

С base64 можно всё совместить. Это же просто форма представления произвольных данных, прям как json, только тот структурирован для удобного доступа к нужным данным, а base64 вместо этого универсально кодирует всё, включая бинарные данные в текст. Правда зачем?

SVG надо рендерить сначала.

peregrine ★★★★★
()
Ответ на: комментарий от peregrine

Я SDL1 взял, ты же прочитал статью. Затем, что sdl.ppu есть изкоробки, а модули sdl2 и sdl3 для паскаля надо ещё поставить. К тому же SDL3 у меня даже сама по себе не установлена.

И мне нужен прямоугольник с пикселями. То есть вывести растр который я сформирую на экран. Зачем мне более сложная либа, если это можно сделать в более простой? Я вообще рассматривал варианты сделать в xterm или ином самый мелкий шрифт (1x1 пиксель) и выводить просто на stdin или взять формат sixtel, но он к сожалению даже в xterm включается только опцией, а не по дефолту. Ещё можно писать в /dev/fb0, но любое обновление экрана будет его нарушать.

Ещё можно посмотреть svgalib, allegro и так далее.

Хотя я почитаю про SDL3, может они снова упростили, но вряд ли оно того стоит, если SDL1 уже установлена и работает.

Xenius ★★★★★
() автор топика
Последнее исправление: Xenius (всего исправлений: 2)
Ответ на: комментарий от Xenius

Так есть же проще варианты: Lazarus (там для ленивых как в дельфях, пусть и жирненькое) и просто openGL взять и текстурку отрендерить. SDL это когда звук, менюшки и прочее надо, но движок готовый брать не хочется.

peregrine ★★★★★
()
Последнее исправление: peregrine (всего исправлений: 1)
Ответ на: комментарий от peregrine

Нет, Lazarus у меня не установлен, только fpc и зачем мне он вообще?

OpenGL можно наверное, но для этого нужна хотя бы минимальная программа как пример, чтобы в своей сделать по аналогии и знать какие мануалы читать. Ну и в SDL я могу создать окно и оно будет с рамочкой и перетаскиваться и мне будут события прилетать. А если прямо на OpenGL делать?

Собственно смысл моей статьи чтобы показать как сделать минимальную и чуть менее минимальную программу для SDL1 на Pascal.

По SDL2 и 3 есть туториал, а по 1 не увидел, может где-то тоже есть.

Xenius ★★★★★
() автор топика
Последнее исправление: Xenius (всего исправлений: 2)
Ответ на: комментарий от Xenius

А если прямо на OpenGL делать?

Сейчас в меня полетят какашки за очень плохой код, который сгенерирован нейронкой (я паскаль лет 10 не видел в глаза) и не использует шейдеры и всё то за что вообще OpenGL используют, но:

program OpenGLTextureGlut;

{$mode objfpc}{$H+}

uses
  gl, glu, glut;

const
  WindowWidth  = 640;
  WindowHeight = 480;

var
  TextureID: GLuint;
  // Массив пикселей 2x2 в формате RGB (Красный, Желтый, Синий, Зеленый)
  TextureData: array[0..3, 0..2] of GLubyte = (
    (255, 0, 0),     // Пиксель 0: Красный
    (255, 255, 0),   // Пиксель 1: Желтый
    (0, 0, 255),     // Пиксель 2: Синий
    (0, 255, 0)      // Пиксель 3: Зеленый
  );

{ Инициализация OpenGL и создание текстуры }
procedure InitGL; cdecl;
begin
  // Устанавливаем черный цвет фона
  glClearColor(0.0, 0.0, 0.0, 0.0);
  
  // Включаем поддержку двумерных текстур
  glEnable(GL_TEXTURE_2D);

  // Генерируем 1 уникальный ID для нашей текстуры
  glGenTextures(1, @TextureID);
  
  // Делаем текстуру активной для последующей настройки
  glBindTexture(GL_TEXTURE_2D, TextureID);

  // Настраиваем фильтрацию (критично для отображения!)
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

  // Загружаем массив пикселей из памяти CPU в память видеокарты
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 2, 2, 0, GL_RGB, GL_UNSIGNED_BYTE, @TextureData);
end;

{ Отрисовка сцены }
procedure Display; cdecl;
begin
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
  glLoadIdentity;

  // Привязываем нашу текстуру перед началом рисования quads
  glBindTexture(GL_TEXTURE_2D, TextureID);

  // Рисуем квадрат и сопоставляем текстурные координаты (от 0.0 до 1.0) с вершинами
  glBegin(GL_QUADS);
    glTexCoord2f(0.0, 0.0); glVertex2f(-0.5, -0.5); // Левый нижний угол
    glTexCoord2f(1.0, 0.0); glVertex2f( 0.5, -0.5); // Правый нижний угол
    glTexCoord2f(1.0, 1.0); glVertex2f( 0.5,  0.5); // Правый верхний угол
    glTexCoord2f(0.0, 1.0); glVertex2f(-0.5,  0.5); // Левый верхний угол
  glEnd;

  glutSwapBuffers;
end;

{ Изменение размеров окна }
procedure Reshape(Width, Height: Integer); cdecl;
begin
  if Height = 0 then Height := 1;
  glViewport(0, 0, Width, Height);
  
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity;
  gluOrtho2D(-1.0, 1.0, -1.0, 1.0);
  
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity;
end;

var
  CmdCount: Integer;
  Cmd: array of PChar;
  I: Integer;

begin
  // Безопасная передача аргументов командной строки в GLUT для Free Pascal
  CmdCount := ParamCount + 1;
  SetLength(Cmd, CmdCount);
  for I := 0 to CmdCount - 1 do
    Cmd[I] := PChar(ParamStr(I));

  // Инициализация GLUT
  glutInit(@CmdCount, @Cmd);
  glutInitDisplayMode(GLUT_DOUBLE or GLUT_RGB or GLUT_DEPTH);
  glutInitWindowSize(WindowWidth, WindowHeight);
  glutCreateWindow('Free Pascal OpenGL Texture');

  // Настройка состояния OpenGL
  InitGL;

  // Регистрация функций обратного вызова
  glutDisplayFunc(@Display);
  glutReshapeFunc(@Reshape);

  // Запуск главного цикла
  glutMainLoop;
end.

Для работы ему надо:

sudo apt install fpc freeglut3-dev mesa-common-dev

Компилировать можно как-то так:

fpc -Mobjfpc -Sh -O2 graphics.pas

Будет проблема с запуском (у паскаля линковщик не сишный, а свой говёный), так что потребует костыликов или курения доков паскаля (мне лень), поэтому в папке проекта:

ln -s /lib/x86_64-linux-gnu/libglut.so.3.12 ./libglut.so.3

Запускать командой

LD_LIBRARY_PATH=. ./graphics

Это концепт просто, моя заслуга только в том, что я гугловской нейронке 5 вопросов задал. И никаких чудовищных SDL-ей, которые даже музыку умеют играть.

ЗЫ

Не уверен, что в коде нет лишних uses.

peregrine ★★★★★
()
Последнее исправление: peregrine (всего исправлений: 2)
Ответ на: комментарий от peregrine

Выглядит так себе. Много букв, какую-то библиотеку ставить.

Xenius ★★★★★
() автор топика
Ответ на: комментарий от peregrine

Правда зачем?

да по мотивам этого треда https://www.linux.org.ru/forum/talks/18282563.
просто я знаю что на canvas можно рисовать поверх практически любого компонента в рамках паскаля и AnyCL-подобных библиотек. ну а svg для текстового представления каракуль нурбсами.

etwrq ★★★★★
()

А зачем? Почему бы не взять pascalABC.net и нарисовать какую угодно графику на нём?

Ygor ★★★★★
()

АВТОР УГАРАЕТ ПО МАТЕМАТИКЕ!!! — Этож круто!

Рисовать фракталы прикольно, и Фрипаскаль я увожаю.

Совет автору: Обрати пожалуйста внимание на такую ООП-Вещь, как «Постороение Графа» — Это крайне-полезная практика, для таких направлений, как создание Графа БлюПринтов (Анриал-Энжин), Блендерского NodeGraph, Нейросетей, да и вообще, — полезно для развития навыков в ООП.

Set440 ★★
()
Ответ на: комментарий от Set440

Плюс в пайпваре тоже для устройств предусмотрена джековская система графов.

…И мат-функции тож можно в графы соединять

Set440 ★★
()
Ответ на: комментарий от Ygor

А зачем? Почему бы не взять pascalABC.net и нарисовать какую угодно графику на нём?

Потому что оно на .NET и компилирует в него же, а у нас тут, пока что, не microsoft.org.ru, я надеюсь.

Что они опакетили для линукса я в курсе, но ставить четверть гигабайта мусора, чтобы рисовать пиксели на экране, с чем мегабайтный Turbo Pascal справлялся лучше — это безумие.

Кроме того, PascalABC.NET несовместим с языком Pascal и вместо паскаля там по факту C# с паскалеподобным синтаксисом. Программы между ABC и нормальным паскалем практически не переносимы.

А если пишешь под Windows, то C# использовать логичнее, поскольку компилятор включен в состав пакета .NET и не нужно ставить никаких дополнительных IDE и прочего хлама, пишешь в любом редакторе, компилируешь и запускаешь.

Xenius ★★★★★
() автор топика
Последнее исправление: Xenius (всего исправлений: 2)
Ответ на: комментарий от Xenius

а у нас тут, пока что, не microsoft.org.ru

Тут буквально opensource.ru

Кроме того, PascalABC.NET несовместим с языком Pascal

Если цель писать на паскале, как на языке разработки - это очень странная цель паскаль, как язык разработки давно мёртв. От этого кода так и тянет Сишкой по низам, наверное лучше взять её, для написания этой программы она тут будет смотреться логичнее. Если цель обучающий материал - так работа с графическим тулкитом win32 на псевдопаскале куда более ценный учебный материал, потому что принятые там подходы работы с тулкитом распространены почти везде в графических тулкитах.

Ygor ★★★★★
()
Ответ на: комментарий от Xenius

PascalABC.NET несовместим с языком Pascal

FreePascal тоже несовместим с языком Pascal, если не включить режим совместимости.

algo
()
Ответ на: комментарий от Ygor

паскаль, как язык разработки давно мёртв

Паскаль мёртв как язык разработки для корпоративного сектора индустрии. Последняя полуживая (хорошая!) реализация – Pascal для OpenVMS.

https://docs.vmssoftware.com/vsi-pascal-v6-5-148-for-openvms-x86-64-release-notes/#known-problems-and-restrictions-section

https://docs.vmssoftware.com/test-docs/vsi-pascal-for-openvms-reference-manual.pdf

IBM уже несколько лет не выдаёт новые лицензии на свой Pascal для своих классических мейнфреймов.

https://www.ibm.com/docs/en/announcements/software-withdrawal-select-z-platform-programs-some-replacements-available-2024-12-03?region=EU

Program name and identifierVRMEffective dateReplacement program name, VRM, and identifierProduct lifecycle page of withdrawn program
VS Pascal Compiler and Library (5668-767)1.x.x3 March 2025None5668-767
VS Pascal Library (5668-717)1.x.x3 March 2025None5668-717

На традиционных UNISYSовских платформах, конечно, есть Pascal для тех, кому он нужен, но всегда был и полноценный Algol

https://public.support.unisys.com/aseries/docs/ClearPath-MCP-22.0/86000080-106/86000080-106.pdf

Но в целом как язык программирования Паскаль ещё жив: появляются новые реализации, возрождаются старые…

Паскаль всегда держался в основном на энтузиастах. В том числе потому, что почти нет платформ, на которых он был бы незаменим.

vM ★★★
()
Ответ на: комментарий от hobbit

Да нет, в данном случае нахождение в неподтверждённых – нормально, может, действительно автор учтёт какую-то критику до подтверждения.

Только где же эта критика? В целом, что хотел я написал. В конце описаны недостатки финальной версии программы.

@dataman, как думаешь, в таком виде пойдёт или ещё что-то надо допилить?

На самом деле я вообще перешел на фракталы, а дописывать график функции для исправления именно этой мне немного лень и не уверен, что это вообще кому-то нужно. Мне — не особо.

Автору: я убрал основную часть текста под спойлер, в окончательном варианте это должно остаться.

А, это чтобы в ленте она не занимала много места?

Xenius ★★★★★
() автор топика
Последнее исправление: Xenius (всего исправлений: 1)
Ответ на: комментарий от Xenius

А, это чтобы в ленте она не занимала много места?

У нас понятие ленты для статей весьма условное. По умолчанию отображается весь текст, как и для новостей. Чтобы получить именно страницу с обзором статей – приходится прятать под кат, а сверху оставлять что-то типа аннотации.

@dataman, как думаешь, в таком виде пойдёт или ещё что-то надо допилить?

Я не @dataman, но думаю, что пойдёт. Подтвердил.

hobbit ★★★★★
()
Ответ на: комментарий от hobbit

А может быть перетасовать изображение чуток и график функции воткнуть первым? Какой-то интерфейс непонятный для этого.

Xenius ★★★★★
() автор топика
Ответ на: комментарий от Reset

В Кумире пиксельная графика делается очень просто: http://qumir.dev/s/MzBjZjgyNTcy

Это хорошо, конечно, но в России сайт частично заблокирован (отображается, но код не подгружается и ни один элемент не работает), а в других странах Кумир вряд ли кому-то нужен.

Хотя конечно продвинутые школьники могут осилить какой-нибудь способ обхода, но есть же полуофициальная IDE на Qt. Правда там нет компилятора, а код сохраняется без форматирования.

Интересно, если написать в Роскомпозор, это поможет?

Xenius ★★★★★
() автор топика
Последнее исправление: Xenius (всего исправлений: 1)
Ответ на: комментарий от Reset

Да причём тут браузер? Роскомпозор пропускает первые 15 килобайт, а потом режет. В итоге https://qumir.dev/vendor/codemirror/codemirror.js и https://qumir.dev/app.js не загружаются. Остальное меньше 15 килобайт и загружается.

Xenius ★★★★★
() автор топика
Ответ на: комментарий от Reset

Тут дело не в браузерах, а в блокировках на ТСПУ.

peregrine ★★★★★
()
Ответ на: комментарий от Xenius

Я пока не знаю с чем это связано. Я поставил Москву в adguardvpn - грузится. Попросил человека в Москве проверить - грузится.

Reset ★★★★★
()
Ответ на: комментарий от Xenius

Да, время летит. Помню его когда он ещё был не net, потом видел какие-то новости о том что net делают проприетарный. А вообще паскаль мёртв. Из нужных языков остались C/C++/Rust/C#/Java/Python/SQL. Всё остальное пожалуй либо совсем ненужно и вредно, либо очень нишевое (Go для микросервисов вместо монструозной Java или JS для веба).

peregrine ★★★★★
()
Ответ на: комментарий от peregrine

Помню его когда он ещё был не net

В институте на компах стоял. Проблема с PascalABC.NET в том, что они считают нормальный Pascal «устаревшим» и не хотят исправлять несовместимости.

А вообще паскаль мёртв.

Ну я бы понял бы ещё если бы ты сказал, что MUMPS или Cobol мёртвые, и то кто-то пользуется, но Pascal вполне себе живой, да и даже Fortran и даже Algol. В gcc недавно включили компилятор Algol 68. И Modula 2 там есть.

C/C++/Rust/C#/Java/Python/SQL

C# и Java не нужны. На андроиде вроде в apk можно нативный код пихать.

SQL — не программирование.

Xenius ★★★★★
() автор топика
Последнее исправление: Xenius (всего исправлений: 1)
Ответ на: комментарий от Reset

Я поставил Москву в adguardvpn - грузится. Попросил человека в Москве проверить - грузится.

Может у него VPN или иной способ обхода настроен настроен? Я тоже спросил — при нажатии на запуск ничего не происходит не только у меня.

Xenius ★★★★★
() автор топика
Последнее исправление: Xenius (всего исправлений: 1)

Первый блин комом

А вот второй, второй уже ехе-шником.

Анекдот из тех же времен, что и мои начала в программировании на TurboPascal.

У меня одноклассник олимпиадник, даже свою гуевую библиотеку(под дос естественно) с кнопочками с тенями помню написал. В то время это прямо вау эффект вызывало при нажатии на них мышкой и когда они реально нажимались.

Loki13 ★★★★★
()

Какую LLM использовал для написания программы? (Ты же в курсе для под паскаль есть MCPсервер?)

impfp
()
Ответ на: комментарий от Xenius

Ничего нет. Видимо работает только в Москве :) Я постараюсь в выходные мигрировать на русский хостинг.

Reset ★★★★★
()
Ответ на: комментарий от Xenius

C# и Java не нужны

Очень нужны везде где производительность не супер-пупер критична, т.е. в 95% всего софта. Хотя C# и с производительностью умеет. А вот дырявая сишка та должна сгинуть всюду, оставив за собой растишку.

peregrine ★★★★★
()
Ответ на: комментарий от Reset

Пусть лучше ИИшница с компилятором воюет. Один фиг мы доживаем эпоху когда люди что-то пиушт, будь то программы, книги или музыка. Думаю лет через 6 ничего этого не останется, кроме какого-то хобби по фану, ну или в очень отсталых странах останется ручное, куда интернет не пустят и на сервера для локальных ИИ денег не найдут.

peregrine ★★★★★
()
Последнее исправление: peregrine (всего исправлений: 3)
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.