From 51108b220a2f232cd7649d58d7a1abf95012a9c5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Deuc=D0=B5?= <shurd@sasktel.net>
Date: Tue, 1 Oct 2024 03:09:18 -0400
Subject: [PATCH] Initial support for SyncTERM PPM/PBM graphics.

Hidden behind the graphics option for now since there's still
issues... the board is offset in the window, the level 1 board is
framed wrong (since the graphics are always 2 cols), and it doesn't
check the graphical resolution, so if you're in a weird mode, it
can break.

Also, the graphics are uninspired at best... just hacked them up
in Gimp.
---
 xtrn/minesweeper/graphics.ppm   | Bin 0 -> 12348 bytes
 xtrn/minesweeper/minesweeper.js | 222 +++++++++++++++++++++++++++++++-
 xtrn/minesweeper/selmask.pbm    |   4 +
 3 files changed, 221 insertions(+), 5 deletions(-)
 create mode 100644 xtrn/minesweeper/graphics.ppm
 create mode 100644 xtrn/minesweeper/selmask.pbm

diff --git a/xtrn/minesweeper/graphics.ppm b/xtrn/minesweeper/graphics.ppm
new file mode 100644
index 0000000000000000000000000000000000000000..a723d98ffb831ed78d38b8d6eb9cc49a28d8d874
GIT binary patch
literal 12348
zcmWGA<5E^|E=o--Nlj5ms#I|I^bJrbOD!tS%+FIW(la#BGd5EQ@bgtD$SF<N&CKI6
zGBr~$G~+TdHRWO$1*0J_vO{1eDS&~2ffVICN7dsGt`QhM|49LZDtxT1tx0mvsCxW~
zpVaW#xs$Z=gjDnCqn?!H{~wax{xdKTgD7!-IU!&T1`zk`+_{sG@^Y|pM1C1nj}`)G
z;e$V?z-0$*_(Resnjaxth!otvkn|6c!b3uoLHr9bfgp*T{_&U$k%GGi61EUQI0?yc
zaBg`y&h(6yGvQjWnvdxP{Ob8wS|O;syd1*dV`+tG!D@azD+4CNqrSYnoWa)@89@}|
z@-Kq`A0~om!Rj8kIS^TEYio#NxHu$!AyGgK30Ds{2f|=rV1O{-B*aEs>LC#USB&W%
zxKv0812Te(qnQse@IQt#;No!gkc1642NwgP4B~#cA#er*B!Qxm)Jp$wQ<2>UcRwVW
zAW~?FAHs!5VO0-zKQaT4dgL^Z%*GX8n2Eoh6`C-xx(7Ms!`XP;gPHgl1o-f%hggb6
zVpR_bRe~fWJ;3W<NV13bUqRU!mI5J`qNy)0FUJgdtnPsbBBy*f8zO~iKHOx80%W_8
z;|r3o;c9R(Aj%-_hnoyZ_Hh4#nn&=&%D_OC^p9lTe`L2|`WF=1V3$FZK@va1e|+f;
zq7p)awSqK3xLD0cPV>lY<nV-~8Hge9^bg@8lUU71OY;QPBP)RC1&AP4^C5zeVjr1}
z>3)bW2$GQW08js*D1fI1FazQkO!Fb)@GyrkAc7DQ;yg6-k<&afo1pn<i62=#Bw<4=
z#X~}rLEHmzErbO75<x%&AtdGLA35<eFc41v$nFOvWw1LSf>^^7Iat7IL7MQWho^aD
z1|IeB^bco16eEW}B#lAD;prdFfQaK#50^zt{J7LZ0s)PL7y=Q5#4p52#E@|H5JMp8
z;QxO}K?vayD1YF=j@DK|a}UIPWRjTlj~Tyk6X51U5;j~M7XzXU;$OI%AwER&FI<-L
z^bc_iJQqN?5EAZwNHjqN;Up*>gWUla!KEIo3a%W)fM~&`9y#Sh*!b1MQ$C!5M?E~*
zBQx-*he&}+U06o|E`rPb5OFw(OFbl136hZX04e_=i2*B#D?h?*hr|M$OHe&p;>WKZ
zVmPk+1xeV*R%2yDltKIpF%YXyA%YME1C{<ki5TnxWRE~%9O8aRd?Cvs7Zeb2g6biO
zAAkQBVibfVDgA?zG^`^G(T>MGn1w&C^Z+r0cI_WX?4ywc-49U!={rE!xZDqs#Z3Ho
z)PoZOJVih&BV_fEgbgtn4+&8QNk4Ei(R>Qw!es{}{lmSA<`+oRL)<@{(m&S3j~u^{
zG8_^bkU*ea`UhoONFsod5PNaC2QCZIh|GmJ6;FI2E5>C$JSpNS-yjJa%`{9dL>a_A
zaAQ&0d0;OiOHrQw;VmCz2E=Vx!v~)Dkr{Z@BfA#P#-ko01tEzk|KVjmG6SL=yZd2f
zK9TJoT!|l7dVpApMq+gjB-aomA?X2Lo<PcQa6$kP$f+7)DVlnS0+3P|hByY7`ysMK
zmj7rGik1c7?tvt1h{<?Jh%$)#;U?31{0BMN!`To=!rc#vBZwe!vWK%FQdrf)JpyMy
z6ypznh#F$je|dR1a>|Dfe&R77Ips6>`a<l&rJmIE4>t!A8jy+rVg*D562FijC5D8n
zhZq7UAsGSAh1-j!9+v7+XoxN}_do<8Br)k9GeRMH;o@-fAqg8Ii-&|LgSa1V2%G`Q
z2yiZhL3#O)$31ZOL!t?fN{G~`dPq3nBGJMJ5~>7ANP2*m|F{gtmHug`9+CpFnh%kJ
z`xlb1v8qHCgeZgf7h(cIl9KcfaUMYu;+~y5cM?<yQ820=5)QaXc=!-Z`49zg^|%Z~
z6CZ@?AqksE65@V>DIcN$IsK!B11>Jad|a}a;-l&@!vtA`yzqgfZDL6B%tv-1el{J<
MCo<(jl+oHf0BEpo4FCWD

literal 0
HcmV?d00001

diff --git a/xtrn/minesweeper/minesweeper.js b/xtrn/minesweeper/minesweeper.js
index a3dd700f79..2d07bbbae7 100644
--- a/xtrn/minesweeper/minesweeper.js
+++ b/xtrn/minesweeper/minesweeper.js
@@ -6,7 +6,7 @@
 
 const title = "Synchronet Minesweeper";
 const ini_section = "minesweeper";
-const REVISION = "$Revision: 2.15 $".split(' ')[1];
+const REVISION = "$Revision: 2.16 $".split(' ')[1];
 const author = "Digital Man";
 const header_height = 4;
 const winners_list = js.exec_dir + "winners.jsonl";
@@ -33,6 +33,26 @@ const winner_subject = "Winner";
 const highscores_subject = "High Scores";
 const tear_line = "\r\n--- " + js.exec_file + " " + REVISION + "\r\n";
 const selectors = ["()", "[]", "<>", "{}", "--", "  "];
+const gfile = "graphics.ppm";
+const mfile = "selmask.pbm";
+const image = {
+	'selected': 208
+};
+image[attr_count + '1'] = 0;
+image[attr_count + '2'] = 16;
+image[attr_count + '3'] = 32;
+image[attr_count + '4'] = 48;
+image[attr_count + '5'] = 64;
+image[attr_count + '6'] = 80;
+image[attr_count + '7'] = 96;
+image[attr_count + '8'] = 112;
+image[char_empty] = 128;
+image[char_covered] = 240;
+image[char_mine] = 144;
+image[char_flag] = 160;
+image[char_unsure] = 176;
+image[char_badflag] = 192;
+image[char_detonated_mine] = 224;
 
 require("sbbsdefs.js", "K_NONE");
 require("mouse_getkey.js", "mouse_getkey");
@@ -87,6 +107,7 @@ var win_rank = false;
 var view_details = false;
 var cell_width;	// either 3 or 2
 var best = null;
+var graph = false;
 
 log(LOG_DEBUG, title + " options: " + JSON.stringify(options));
 
@@ -544,7 +565,18 @@ function draw_cell(x, y)
 		left = "\x01n\x01h" + selectors[selector%selectors.length][0];
 		right = "\x01n\x01h" + selectors[selector%selectors.length][1];
 	}
-	console.print(left + val + right);
+	if (graph) {
+		const margin = Math.floor((console.screen_columns - (game.width * cell_width)) / 2);
+		var xpos = (x * cell_width + margin) * 8;
+		var ypos = (header_height + y + top) * 16;
+		console.write('\x1b_SyncTERM:P;Paste;SX='+image[val]+';SY=0;SW=16;SH=16;DX='+xpos+';DY='+ypos+';B=0\x1b\\');
+		if (selected.x == x && selected.y == y) {
+			console.write('\x1b_SyncTERM:P;Paste;SX='+image['selected']+';SY=0;SW=16;SH=16;DX='+xpos+';DY='+ypos+';MBUF;B=0\x1b\\');
+		}
+	}
+	else {
+		console.print(left + val + right);
+	}
 }
 
 // Return total number of surrounding flags
@@ -711,7 +743,9 @@ function draw_board(full)
 			draw_border();
 		for(var x = 0; x < game.width; x++) {
 			if(full || board[y][x].changed !== false) {
-				if(console.term_supports(USER_ANSI))
+				if (graph) {
+				}
+				else if(console.term_supports(USER_ANSI))
 					console.gotoxy((x * cell_width) + margin + 1, header_height + y + top + 1);
 				else {
 					console.creturn();
@@ -743,7 +777,9 @@ function draw_board(full)
 		console.attributes = LIGHTGRAY;
 	}
 	if(redraw_selection) { // We need to draw/redraw the selected cell last in this case
-		if(console.term_supports(USER_ANSI))
+		if (graph) {
+		}
+		else if(console.term_supports(USER_ANSI))
 			console.gotoxy(margin + (selected.x * cell_width) + 1, header_height + selected.y + top + 1);
 		else {
 			console.up(height - (selected.y + 1));
@@ -885,7 +921,7 @@ function init_game(difficulty)
 	game.width = game.height;
 	game.height = Math.min(game.height, console.screen_rows - header_height);
 	game.width += game.width - game.height;
-	if(game.width > 10 && (game.width * 3) + 2 > (console.screen_columns - 20))
+	if(graph || (game.width > 10 && (game.width * 3) + 2 > (console.screen_columns - 20)))
 		cell_width = 2;
 	else
 		cell_width = 3;
@@ -946,8 +982,181 @@ function screen_to_board(mouse)
 	return true;
 }
 
+function read_apc()
+{
+	var ret = '';
+	var ch;
+	var state = 0;
+
+	for(;;) {
+		ch = console.getbyte(1000);
+		if (ch === null)
+			return undefined;
+		switch(state) {
+			case 0:
+				if (ch == 0x1b) {
+					state++;
+					break;
+				}
+				break;
+			case 1:
+				if (ch == 95) {
+					state++;
+					break;
+				}
+				state = 0;
+				break;
+			case 2:
+				if (ch == 0x1b) {
+					state++;
+					break;
+				}
+				ret += ascii(ch);
+				break;
+			case 3:
+				if (ch == 92) {
+					return ret;
+				}
+				return undefined;
+		}
+	}
+	return undefined;
+}
+
+
+function pixel_capability()
+{
+	var ret = false;
+	var ch;
+	var state = 0;
+	var optval = 0;
+
+	for(;;) {
+		ch = console.getbyte();
+		switch(state) {
+			case 0:
+				if (ch == 0x1b) { // ESC
+					state++;
+					break;
+				}
+				break;
+			case 1:
+				if (ch == 91) {   // [
+					state++;
+					break;
+				}
+				state = 0;
+				break;
+			case 2:
+				if (ch == 60) {   // <
+					state++;
+					break;
+				}
+				state = 0;
+				break;
+			case 3:
+				if (ch == 48) {   // 0
+					state++;
+					break;
+				}
+				state = 0;
+				break;
+			case 4:
+				if (ch == 59) {   // ;
+					state++;
+					break;
+				}
+				state = 0;
+				break;
+			case 5:
+				if (ch >= ascii('0') && ch <= ascii('9')) {
+					optval = optval * 10 + (ch - ascii('0'));
+					break;
+				}
+				else if(ch == 59) {
+					if (optval === 3)
+						ret = true;
+					optval = 0;
+					break;
+				}
+				else if (ch === 99) { // c
+					if (optval === 3)
+						ret = true;
+					return ret;
+				}
+				state = 0;
+				break;
+		}
+	}
+	return ret;
+}
+
+function detect_graphics()
+{
+	var tmpckpt;
+	var f;
+	var md5;
+	var lst;
+	var m;
+	var b;
+
+	// Detect PPM graphics and load the cache
+	graph = false;
+	tmpckpt = console.ctrlkey_passthru;
+	if (console.cterm_version >= 1316) {
+		console.ctrlkey_passthru = '+[';
+		console.write('\x1b[<c');
+		graph = pixel_capability();
+	}
+
+	if (graph) {
+		// Load up cache...
+		f = new File(js.exec_dir+'/'+gfile);
+		if (f.open('rb')) {
+			md5 = f.md5_hex;
+			console.write('\x1b_SyncTERM:C;L;minesweeper/'+gfile+'\x1b\\');
+			lst = read_apc();
+			m = lst.match(/\ngraphics.ppm\t([0-9a-f]+)\n/);
+			if (m == null || m[1] !== md5) {
+				// Store in cache...
+				console.write('\x1b_SyncTERM:C;S;minesweeper/'+gfile+';');
+				f.base64 = true;
+				console.write(f.read());
+				console.write('\x1b\\');
+			}
+			f.close();
+			console.write('\x1b_SyncTERM:C;LoadPPM;B=0;minesweeper/'+gfile+'\x1b\\');
+			f = new File(js.exec_dir+'/'+mfile);
+			if (f.open('rb')) {
+				md5 = f.md5_hex;
+				console.write('\x1b_SyncTERM:C;L;minesweeper/'+mfile+'\x1b\\');
+				lst = read_apc();
+				m = lst.match(/\nselmask.pbm\t([0-9a-f]+)\n/);
+				if (m == null || m[1] !== md5) {
+					// Store in cache...
+					console.write('\x1b_SyncTERM:C;S;minesweeper/'+mfile+';');
+					f.base64 = true;
+					console.write(f.read());
+					console.write('\x1b\\');
+				}
+				f.close();
+				console.write('\x1b_SyncTERM:C;LoadPBM;minesweeper/'+mfile+'\x1b\\');
+			}
+			else {
+				graph = false;
+			}
+		}
+		else {
+			graph = false;
+		}
+	}
+	console.ctrlkey_passthru = tmpckpt;
+}
+
 function play()
 {
+	if (graph)
+		detect_graphics();
 	console.clear();
 	var start = Date.now();
 	show_image(welcome_image, /* fx: */false, /* delay: */0);
@@ -1253,6 +1462,9 @@ try {
 
 		js.on_exit("console.attributes = LIGHTGRAY");
 	}
+	if(argv.indexOf("graphics") >= 0) {
+		graph = true;
+	}
 	if(argv.indexOf("winners") >= 0) {
 		if(!isNaN(numval) && numval > 0)
 			options.winners = numval;
diff --git a/xtrn/minesweeper/selmask.pbm b/xtrn/minesweeper/selmask.pbm
new file mode 100644
index 0000000000..9e9cc16d5b
--- /dev/null
+++ b/xtrn/minesweeper/selmask.pbm
@@ -0,0 +1,4 @@
+P4
+# Created by GIMP version 2.10.36 PNM plug-in
+16 16
+��������������������
\ No newline at end of file
-- 
GitLab