shithub: riscv

Download patch

ref: 4aff59b64ca7be9c003883b97d3bbdcd56dc61bc
parent: f0a314605f1a6d56da34ac07bb4effe2dcff8c37
author: aiju <devnull@localhost>
date: Sat Feb 24 21:50:24 EST 2018

ghost in the minesweeper shell

--- a/sys/src/games/mines/dat.h
+++ b/sys/src/games/mines/dat.h
@@ -46,13 +46,11 @@
 	int Mine, Picture, Neighbours;
 } FieldCell;
 
-struct {
-	int MaxX, MaxY, Mines;
-} Settings[] = { {8, 8, 10}, {16, 16, 40}, {30, 16, 99}, {0, 0, 0} };
 
-extern int MaxX, MaxY, Mines, Level, UnknownCell, Playing, MinesRemain, Time, Status, UseQuery, UseColor;
+extern int MaxX, MaxY, Mines, Level, UnknownCell, Playing, MinesRemain, Time, Status, UseQuery, UseGhost, UseColor;
 extern Point Origin;
 extern FieldCell **MineField;
+extern Mouse LastMouse;
 
 extern uchar SrcDigit0[];
 extern uchar SrcDigit1[];
--- /dev/null
+++ b/sys/src/games/mines/fns.h
@@ -1,0 +1,6 @@
+void LeftClick(Point);
+void RightClick(Point);
+void DrawButton(int);
+void InitMineField(void);
+void GhostMode(void);
+void GhostReset(void);
--- /dev/null
+++ b/sys/src/games/mines/ghost.c
@@ -1,0 +1,283 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include "dat.h"
+#include "fns.h"
+
+enum {
+	MEmpty,
+	MMine,
+	MUnknown,
+	NState,
+};
+
+int ghostactive;
+int ghostwait;
+Point ghosttarget;
+
+static uchar ***
+neighbours(uchar **f, uchar ***n)
+{
+	int x, y, p;
+
+	if(n != nil){
+		for(x = 0; x < MaxX; x++)
+			for(y = 0; y < MaxY; y++)
+				memset(n[x][y], 0, NState);
+	}else{
+		n = calloc(sizeof(void*), MaxX);
+		for(x = 0; x < MaxX; x++){
+			n[x] = calloc(sizeof(void*), MaxY);
+			for(y = 0; y < MaxY; y++)
+				n[x][y] = calloc(sizeof(uchar), NState);
+		}
+	}
+	
+	for(y = 0; y < MaxY; y++)
+		for(x = 0; x < MaxX; x++){
+			p = f[x][y];
+			if(x > 0 && y > 0) n[x-1][y-1][p]++;
+			if(y > 0) n[x][y-1][p]++;
+			if(x < MaxX-1 && y > 0) n[x+1][y-1][p]++;
+			if(x > 0) n[x-1][y][p]++;
+			if(x < MaxX-1) n[x+1][y][p]++;
+			if(x > 0 && y < MaxY-1) n[x-1][y+1][p]++;
+			if(y < MaxY-1) n[x][y+1][p]++;
+			if(x < MaxX-1 && y < MaxY-1) n[x+1][y+1][p]++;
+		}
+	return n;
+}
+
+static void
+freeneighbours(uchar ***n)
+{
+	int x, y;
+	
+	if(n == nil)
+		return;
+	for(x = 0; x < MaxX; x++){
+		for(y = 0; y < MaxY; y++)
+			free(n[x][y]);
+		free(n[x]);
+	}
+	free(n);
+}
+
+static int
+allneighbours(uchar **f, int x, int y, int (*fun)(uchar **, int, int, void *), void *aux)
+{
+	int rc;
+
+	rc = 0;
+	if(x > 0 && y > 0) rc += fun(f, x-1, y-1, aux);
+	if(y > 0) rc += fun(f, x, y-1, aux);
+	if(x < MaxX-1 && y > 0) rc += fun(f, x+1, y-1, aux);
+	if(x > 0) rc += fun(f, x-1, y, aux);
+	if(x < MaxX-1) rc += fun(f, x+1, y, aux);
+	if(x > 0 && y < MaxY-1) rc += fun(f, x-1, y+1, aux);
+	if(y < MaxY-1) rc += fun(f, x, y+1, aux);
+	if(x < MaxX-1 && y < MaxY-1) rc += fun(f, x+1, y+1, aux);
+	return rc;
+}
+
+typedef struct {
+	int mines, pts;
+	Point pt[8];
+} CList;
+
+static int
+addlist(uchar **f, int x, int y, void *aux)
+{
+	CList *c;
+	
+	c = aux;
+	if(f[x][y] == MUnknown)
+		c->pt[c->pts++] = (Point){x,y};
+	return 0;
+}
+
+static void
+mklists(uchar **f, CList **clp, int *nclp)
+{
+	CList *cl;
+	int ncl, x, y;
+	uchar ***nei;
+	
+	cl = nil;
+	ncl = 0;
+	nei = neighbours(f, nil);
+	for(y = 0; y < MaxY; y++)
+		for(x = 0; x < MaxX; x++)
+			if(MineField[x][y].Picture <= Empty8 && nei[x][y][MUnknown] > 0){
+				cl = realloc(cl, (ncl + 1) * sizeof(CList));
+				memset(&cl[ncl], 0, sizeof(CList));
+				cl[ncl].mines = MineField[x][y].Picture - nei[x][y][MMine];
+				allneighbours(f, x, y, addlist, &cl[ncl]);
+				ncl++;
+			}
+	freeneighbours(nei);
+	*clp = cl;
+	*nclp = ncl;
+}
+
+static int
+ismember(CList *c, Point p)
+{
+	int i;
+	
+	for(i = 0; i < c->pts; i++)
+		if(c->pt[i].x == p.x && c->pt[i].y == p.y)
+			return 1;
+	return 0;
+}
+
+static void
+merge(CList *cl, int *nclp)
+{
+	int i, j, k, l;
+
+start:
+	for(i = 0; i < *nclp; i++)
+		for(j = 0; j < *nclp; j++){
+			if(i == j) continue;
+			for(k = 0; k < cl[i].pts; k++)
+				if(!ismember(&cl[j], cl[i].pt[k]))
+					goto next;		
+			for(k = l = 0; k < cl[j].pts; k++)
+				if(!ismember(&cl[i], cl[j].pt[k]))
+					cl[j].pt[l++] = cl[j].pt[k];
+			cl[j].pts = l;
+			cl[j].mines -= cl[i].mines;
+			if(l == 0){
+				memcpy(&cl[j], &cl[j+1], (*nclp - j - 1) * sizeof(CList));
+				(*nclp)--;
+			}
+			goto start;
+		next: ;
+		}	
+}
+
+static void
+ghostfind(void)
+{
+	int x, y, i, j, n;
+	uchar **field;
+	CList *cl;
+	int ncl;
+	Point pd;
+	int d, min;
+
+	field = calloc(sizeof(uchar*), MaxX);
+	for(x = 0; x < MaxX; x++){
+		field[x] = calloc(sizeof(uchar), MaxY);
+		for(y = 0; y < MaxY; y++)
+			switch(MineField[x][y].Picture){
+			case Empty0:
+			case Empty1:
+			case Empty2:
+			case Empty3:
+			case Empty4:
+			case Empty5:
+			case Empty6:
+			case Empty7:
+			case Empty8:
+				field[x][y] = MEmpty;
+				break;
+			case Mark:
+				field[x][y] = MMine;
+				break;
+			default:
+				field[x][y] = MUnknown;
+			}
+	}
+	
+	mklists(field, &cl, &ncl);
+	merge(cl, &ncl);
+	ghostactive = -1;
+	min = 0;
+	for(i = 0; i < ncl; i++)
+		if(cl[i].mines == 0 || cl[i].mines == cl[i].pts)
+			for(j = 0; j < cl[i].pts; j++){
+				pd = subpt(addpt(addpt(mulpt(cl[i].pt[j], 16), Pt(12+8, 57+8)), Origin), LastMouse.xy);
+				d = pd.x * pd.x + pd.y * pd.y;
+				if(ghostactive < 0 || d < min){
+					ghostactive = 1 + (cl[i].mines == cl[i].pts);
+					ghosttarget = cl[i].pt[j];
+					min = d;
+				}
+				field[cl[i].pt[j].x][cl[i].pt[j].y] = cl[i].mines == cl[i].pts ? MMine : MEmpty;
+			}
+	if(ghostactive < 0){
+		n = 0;
+		for(x = 0; x < MaxX; x++)
+			for(y = 0; y < MaxY; y++)
+				if(field[x][y] == MUnknown)
+					n++;
+		if(n == 0) goto done;
+		n = lrand() % n;
+		for(x = 0; x < MaxX; x++)
+			for(y = 0; y < MaxY; y++)
+				if(field[x][y] == MUnknown && n-- == 0){
+					ghostactive = 1;
+					ghosttarget = Pt(x, y);
+					goto done;
+				}
+	done:;
+	}
+	for(x = 0; x < MaxX; x++)
+		free(field[x]);
+	free(field);
+	free(cl);
+}
+
+void
+GhostMode(void)
+{
+	Point p, q;
+	double d;
+
+	if(ghostwait > 0){
+		ghostwait--;
+		return;
+	}
+	if(Status != Game){
+		ghostactive = 0;
+		p = Pt(Origin.x +  MaxX * 8 + 12, Origin.y + 28);
+		if(ptinrect(LastMouse.xy, insetrect(Rpt(p, p), -4))){
+			InitMineField();
+			eresized(0);
+		}
+		goto move;
+	}
+	if(!ghostactive)
+		ghostfind();
+	if(ghostactive > 0){
+		p = addpt(addpt(mulpt(ghosttarget, 16), Pt(12+8, 57+8)), Origin);
+		if(ptinrect(LastMouse.xy, insetrect(Rpt(p, p), -4))){
+			switch(ghostactive){
+			case 1: LeftClick(ghosttarget); break;
+			case 2: RightClick(ghosttarget); break;
+			}
+			if(Status != Game) ghostwait = 100;
+			DrawButton(Status);
+			flushimage(display, 1);
+			ghostactive = 0;
+			return;
+		}
+	move:
+		q = subpt(p, LastMouse.xy);
+		d = hypot(q.x, q.y);
+		d = 2 / d * (1 + d / (400 + d));
+		LastMouse.xy.x += ceil(q.x * d);
+		LastMouse.xy.y += ceil(q.y * d);
+		emoveto(LastMouse.xy);
+	}
+}
+
+void
+GhostReset(void)
+{
+	ghostactive = 0;
+	ghostwait = 0;
+}
--- a/sys/src/games/mines/mines.c
+++ b/sys/src/games/mines/mines.c
@@ -3,10 +3,16 @@
 #include <draw.h>
 #include <event.h>
 #include "dat.h"
+#include "fns.h"
 
-int MaxX, MaxY, Mines, Level, UnknownCell, Playing, MinesRemain, Time, Status, UseQuery = TRUE, UseColor = TRUE;
+struct {
+	int MaxX, MaxY, Mines;
+} Settings[] = { {8, 8, 10}, {16, 16, 40}, {30, 16, 99}, {0, 0, 0} };
 
+int MaxX, MaxY, Mines, Level, UnknownCell, Playing, MinesRemain, Time, Status, UseQuery = TRUE, UseGhost = FALSE, UseColor = TRUE;
+
 Point Origin;
+Mouse LastMouse;
 
 Image *RGB000000, *RGB0000FF, *RGB007F00, *RGB7F7F7F, *RGBBFBFBF, *RGBFF0000, *RGBFFFF00, *RGBFFFFFF, *ImageButton[5], *ImageSign, *ImageDigit[10], *ImageCell[16];
 
@@ -374,13 +380,19 @@
 
 void Usage(void) {
 
-	fprint(2, "Usage: %s\n", argv0);
+	fprint(2, "Usage: %s [-aeq]\n", argv0);
 	exits("usage");
 }
 
 void main(int argc, char **argv) {
 
+	Level = Beginner;
+
 	ARGBEGIN {
+	case 'a': Level = Advanced; break;
+	case 'e': Level = Expert; break;
+	case 'q': UseQuery = FALSE; break;
+	case 'g': UseGhost = TRUE; break;
 	default:
 		Usage();
 	} ARGEND
@@ -499,7 +511,7 @@
 
 	srand(time(0)); /* initialize generator of random numbers */
 
-	NewMineField(Beginner);
+	NewMineField(Level);
 
 	eresized(0);
 
@@ -507,22 +519,40 @@
 
 	{
 		int PushButton = FALSE, Button = FALSE, CurrentButton, ChargedButton = FALSE, MiddleButton = FALSE, LastButton = 0;
+		int Counter = 0;
 		ulong Key, Etimer;
+		uvlong LastAction;
 		Event Event;
 		Point CurrentCell, Cell = Pt(-1, -1);
 
-		Etimer = etimer(0, 1000);
+		Etimer = etimer(0, UseGhost ? 10 : 1000);
+		LastAction = nsec();
 
 		for(;;) {
 			Key = event(&Event);
 
 			if(Key == Etimer) {
-
-				if(Playing && Time < INT_MAX)
-					DisplayCounter(Origin.x -34 + MaxX * 16, ++Time);
+			
+				if(nsec() - LastAction > 5000000000ULL && LastMouse.buttons == 0)
+					GhostMode();
+			
+				if(++Counter == (UseGhost ? 100 : 1)){
+				
+					Counter = 0;
+					
+					if(Playing && Time < INT_MAX)
+						DisplayCounter(Origin.x -34 + MaxX * 16, ++Time);
+				
+				}
 			}
 
 			if(Key == Emouse) {
+			
+				if(!eqpt(LastMouse.xy, Event.mouse.xy) || LastMouse.buttons != Event.mouse.buttons){
+					LastAction = nsec();
+					GhostReset();
+				}
+				LastMouse = Event.mouse;
 
 				/* mouse over button? */
 				CurrentButton = FALSE;
@@ -626,6 +656,9 @@
 			}
 
 			if(Key == Ekeyboard) {
+			
+				LastAction = nsec();
+				GhostReset();
 
 				switch(Event.kbdc) {
 					case 'n':
--- a/sys/src/games/mines/mkfile
+++ b/sys/src/games/mines/mkfile
@@ -4,9 +4,10 @@
 
 OFILES=\
 	mines.$O \
+	ghost.$O \
 	gfx.$O \
 
-HFILES=dat.h
+HFILES=dat.h fns.h
 
 BIN=/$objtype/bin/games