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