cgram.c revision 1.1
1/*-
2 * Copyright (c) 2013 The NetBSD Foundation, Inc.
3 * All rights reserved.
4 *
5 * This code is derived from software contributed to The NetBSD Foundation
6 * by David A. Holland.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
18 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
21 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 * POSSIBILITY OF SUCH DAMAGE.
28 */
29
30#include <stdio.h>
31#include <string.h>
32#include <stdlib.h>
33#include <ctype.h>
34#include <time.h>
35#include <err.h>
36#include <assert.h>
37#include <curses.h>
38#include "pathnames.h"
39
40////////////////////////////////////////////////////////////
41
42static char *xstrdup(const char *s) {
43   char *ret;
44
45   ret = malloc(strlen(s) + 1);
46   if (ret == NULL) {
47      errx(1, "Out of memory");
48   }
49   strcpy(ret, s);
50   return ret;
51}
52
53////////////////////////////////////////////////////////////
54
55struct stringarray {
56   char **v;
57   int num;
58};
59
60static void stringarray_init(struct stringarray *a) {
61   a->v = NULL;
62   a->num = 0;
63}
64
65static void stringarray_cleanup(struct stringarray *a) {
66   free(a->v);
67}
68
69static void stringarray_add(struct stringarray *a, const char *s) {
70   a->v = realloc(a->v, (a->num + 1) * sizeof(a->v[0]));
71   if (a->v == NULL) {
72      errx(1, "Out of memory");
73   }
74   a->v[a->num] = xstrdup(s);
75   a->num++;
76}
77
78////////////////////////////////////////////////////////////
79
80static struct stringarray lines;
81static struct stringarray sollines;
82static bool hinting;
83static int scrolldown;
84static unsigned curx;
85static int cury;
86
87static void readquote(void) {
88   FILE *f = popen(_PATH_FORTUNE, "r");
89   if (!f) {
90      err(1, "%s", _PATH_FORTUNE);
91   }
92
93   char buf[128], buf2[8*sizeof(buf)];
94   while (fgets(buf, sizeof(buf), f)) {
95      char *s = strrchr(buf, '\n');
96      assert(s);
97      assert(strlen(s)==1);
98      *s = 0;
99
100      int i,j;
101      for (i=j=0; buf[i]; i++) {
102	 if (buf[i]=='\t') {
103	    buf2[j++] = ' ';
104	    while (j%8) buf2[j++] = ' ';
105	 }
106	 else if (buf[i]=='\b') {
107	    if (j>0) j--;
108	 }
109	 else {
110	    buf2[j++] = buf[i];
111	 }
112      }
113      buf2[j] = 0;
114
115      stringarray_add(&lines, buf2);
116      stringarray_add(&sollines, buf2);
117   }
118
119   pclose(f);
120}
121
122static void encode(void) {
123   int used[26];
124   for (int i=0; i<26; i++) used[i] = 0;
125
126   int key[26];
127   int keypos=0;
128   while (keypos < 26) {
129      int c = random()%26;
130      if (used[c]) continue;
131      key[keypos++] = c;
132      used[c] = 1;
133   }
134
135   for (int y=0; y<lines.num; y++) {
136      for (unsigned x=0; lines.v[y][x]; x++) {
137	 if (islower((unsigned char)lines.v[y][x])) {
138	    int q = lines.v[y][x]-'a';
139	    lines.v[y][x] = 'a'+key[q];
140	 }
141	 if (isupper((unsigned char)lines.v[y][x])) {
142	    int q = lines.v[y][x]-'A';
143	    lines.v[y][x] = 'A'+key[q];
144	 }
145      }
146   }
147}
148
149static int substitute(int ch) {
150   assert(cury>=0 && cury<lines.num);
151   if (curx >= strlen(lines.v[cury])) {
152      beep();
153      return -1;
154   }
155
156   int och = lines.v[cury][curx];
157   if (!isalpha((unsigned char)och)) {
158      beep();
159      return -1;
160   }
161
162   int loch = tolower((unsigned char)och);
163   int uoch = toupper((unsigned char)och);
164   int lch = tolower((unsigned char)ch);
165   int uch = toupper((unsigned char)ch);
166
167   for (int y=0; y<lines.num; y++) {
168      for (unsigned x=0; lines.v[y][x]; x++) {
169	 if (lines.v[y][x]==loch) {
170	    lines.v[y][x] = lch;
171	 }
172	 else if (lines.v[y][x]==uoch) {
173	    lines.v[y][x] = uch;
174	 }
175	 else if (lines.v[y][x]==lch) {
176	    lines.v[y][x] = loch;
177	 }
178	 else if (lines.v[y][x]==uch) {
179	    lines.v[y][x] = uoch;
180	 }
181      }
182   }
183   return 0;
184}
185
186////////////////////////////////////////////////////////////
187
188static void redraw(void) {
189   erase();
190   bool won = true;
191   for (int i=0; i<LINES-1; i++) {
192      move(i, 0);
193      int ln = i+scrolldown;
194      if (ln < lines.num) {
195	 for (unsigned j=0; lines.v[i][j]; j++) {
196	    int ch = lines.v[i][j];
197	    if (ch != sollines.v[i][j] && isalpha((unsigned char)ch)) {
198	       won = false;
199	    }
200	    bool bold=false;
201	    if (hinting && ch==sollines.v[i][j] &&
202		isalpha((unsigned char)ch)) {
203	       bold = true;
204	       attron(A_BOLD);
205	    }
206	    addch(lines.v[i][j]);
207	    if (bold) {
208	       attroff(A_BOLD);
209	    }
210	 }
211      }
212      clrtoeol();
213   }
214
215   move(LINES-1, 0);
216   if (won) {
217      addstr("*solved* ");
218   }
219   addstr("~ to quit, * to cheat, ^pnfb to move");
220
221   move(LINES-1, 0);
222
223   move(cury-scrolldown, curx);
224
225   refresh();
226}
227
228static void opencurses(void) {
229    initscr();
230    cbreak();
231    noecho();
232}
233
234static void closecurses(void) {
235   endwin();
236}
237
238////////////////////////////////////////////////////////////
239
240static void loop(void) {
241   bool done=false;
242   while (!done) {
243      redraw();
244      int ch = getch();
245      switch (ch) {
246       case 1: /* ^A */
247	curx=0;
248	break;
249       case 2: /* ^B */
250	if (curx > 0) {
251	   curx--;
252	}
253	else if (cury > 0) {
254	   cury--;
255	   curx = strlen(lines.v[cury]);
256	}
257	break;
258       case 5: /* ^E */
259	curx = strlen(lines.v[cury]);
260	break;
261       case 6: /* ^F */
262	if (curx < strlen(lines.v[cury])) {
263	   curx++;
264	}
265	else if (cury < lines.num - 1) {
266	   cury++;
267	   curx = 0;
268	}
269	break;
270       case 12: /* ^L */
271	clear();
272	break;
273       case 14: /* ^N */
274	if (cury < lines.num-1) {
275	   cury++;
276	}
277	if (curx > strlen(lines.v[cury])) {
278	   curx =  strlen(lines.v[cury]);
279	}
280	if (scrolldown < cury - (LINES-2)) {
281	   scrolldown = cury - (LINES-2);
282	}
283	break;
284       case 16: /* ^P */
285	if (cury > 0) {
286	   cury--;
287	}
288	if (curx > strlen(lines.v[cury])) {
289	   curx = strlen(lines.v[cury]);
290	}
291	if (scrolldown > cury) {
292	   scrolldown = cury;
293	}
294	break;
295       case '*':
296	hinting = !hinting;
297	break;
298       case '~':
299	done = true;
300	break;
301       default:
302	if (isalpha(ch)) {
303	   if (!substitute(ch)) {
304	      if (curx < strlen(lines.v[cury])) {
305		 curx++;
306	      }
307	      if (curx==strlen(lines.v[cury]) && cury < lines.num-1) {
308		 curx=0;
309		 cury++;
310	      }
311	   }
312	}
313	else if (curx < strlen(lines.v[cury]) && ch==lines.v[cury][curx]) {
314	   curx++;
315	   if (curx==strlen(lines.v[cury]) && cury < lines.num-1) {
316	      curx=0;
317	      cury++;
318	   }
319	}
320	else {
321	   beep();
322	}
323	break;
324      }
325   }
326}
327
328////////////////////////////////////////////////////////////
329
330int main(void) {
331   stringarray_init(&lines);
332   stringarray_init(&sollines);
333   srandom(time(NULL));
334   readquote();
335   encode();
336   opencurses();
337
338   loop();
339
340   closecurses();
341   stringarray_cleanup(&sollines);
342   stringarray_cleanup(&lines);
343   return 0;
344}
345