Update.
[crosswords.git] / Words.java
1 /******************************************************************************
2
3  Cross-Words applet v1.5 (c) Francois FLEURET
4
5  This small applet is an interactive crosswords grid for web
6  pages. Mail <francois.fleuret@inria.fr> for bug reports and
7  questions.
8
9  In the past, I put his applet under a pure GPL licence. So, what was
10  expected to happen happened : commercial sites used it and I saw
11  people earning money with my work without giving me a single
12  buck. This is bad. So, now this software is distributed under the FFL
13  (Francois Fleuret Licence) which is the same as GPL version 2, plus
14  the following paragraph:
15
16  *
17  * RESTRICTION OF USE FOR APPLET SOFTWARES
18  *
19  * 13 The site the applet is used on must be a NON-PROFIT site, which
20  * implies it does not contain banners or any other mean to make money
21  * (including indirectly, for example as an ISP portal). The name of
22  * all the authors of the software and a link to the URL they provide
23  * in the source code must appear on any web page that uses the
24  * applet. If the source code was modified, the modified source must
25  * be downloadable from the site the applet is used on.
26  *
27
28  So, if you use this applet on a non-profit site, please add a link to
29  http://www-rocq.inria.fr/~fleuret and a short note indicating my name
30  as the author of the applet. For any other usage, please contact
31  me. Be sure we can make a deal for a reasonnable price.
32
33  This program is distributed in the hope that it will be useful, but
34  WITHOUT ANY WARRANTY; without even the implied warranty of
35  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
36  General Public License for more details.
37
38                                        Sept 11th 2000, François Fleuret
39
40 ******************************************************************************/
41
42 import java.applet.Applet;
43 import java.awt.*;
44 import java.awt.event.*;
45 import java.lang.Object;
46
47 class Square {
48     char solution;
49     char found;
50     boolean ajour;
51     boolean holdCursor;
52     boolean locked;
53     Color color;
54
55     Square(char c) {
56         solution = c;
57         found = ' ';
58         ajour = false;
59         locked = false;
60         color = Color.black;
61     }
62
63     void setFound(char c) { if(c != found) { found = c; ajour = false; } }
64
65     void putCursor() { if(!holdCursor) ajour = false; holdCursor = true; }
66
67     void remCursor() { if(holdCursor) ajour = false; holdCursor = false; }
68     
69     void lock(Color c) {
70         if(!locked) {
71             ajour = false;
72             locked = true;
73             color = c;
74         }
75     }
76 }
77
78 class Grid extends Panel implements KeyListener, MouseListener {
79     int gridWidth, gridHeight;
80     int xCursor, yCursor;
81     int nbLetters, nbFound;
82     boolean cursorHorizontal;
83
84     int xMargin, yMargin;
85     Square grid[][];
86     Font font, smallFont;
87     FontMetrics fontMetrics, smallFontMetrics;
88     int squareSize;
89
90     TextArea definitionArea;
91     String[][][] definitions;
92
93     Grid(Font f, Font sf,
94          FontMetrics fm, FontMetrics sfm,
95          int w, int h,
96          String content,
97          TextArea ta, String def) {
98
99         int x, y, xx, yy, k;
100
101         font = f; fontMetrics = fm;
102         smallFont = sf; smallFontMetrics = sfm;
103         squareSize = fontMetrics.getHeight();
104
105         gridWidth = w;
106         gridHeight = h;
107         grid = new Square[gridWidth][gridHeight];
108
109         yMargin = smallFontMetrics.getHeight();
110         xMargin = yMargin;
111
112         nbLetters = 0; nbFound = 0;
113         k = 0;
114         for(y = 0; y<gridHeight; y++) for(x = 0; x<gridWidth; x++) {
115             if(k<content.length())
116                 grid[x][y] = new Square(content.charAt(k++));
117             else
118                 grid[x][y] = new Square('.');
119             if(grid[x][y].solution != '.') nbLetters++;
120         }
121
122         xCursor = 0; yCursor = 0;
123         cursorHorizontal = true;
124         grid[xCursor][yCursor].putCursor();
125       
126         if(ta != null) {
127             definitionArea = ta;
128             definitions = new String[gridWidth][gridHeight][2];
129
130             String direction, sd;
131             int i, j;
132
133             direction="h";
134
135             for(y=0; y<gridHeight; y++) for(x=0; x<gridWidth; x++) {
136                 definitions[x][y][0] = "";
137                 definitions[x][y][1] = "";
138             }
139
140             j = 0;
141             while(j < def.length()-1) {
142
143                 i = j; j = def.indexOf('/', i);
144                 if((j > 0) && ((def.indexOf(':', i) > j) || (def.indexOf(':', i) < 0))) {
145                     direction = def.substring(i, j).toLowerCase();
146                     j++;
147                 } else j = i;
148                 
149
150                 i = j;
151                 y = 0;
152                 while((def.charAt(i) >= 'a') && (def.charAt(i) <= 'z'))
153                     y = y*26 + ((int) def.charAt(i++)) - 'a';
154
155                 x = 0;
156                 while((def.charAt(i) >= '0') && (def.charAt(i) <= '9'))
157                     x = x*10 + ((int) def.charAt(i++)) - '0';
158                 x--;
159                 i++;
160
161                 j = def.indexOf('/', i);
162                 if(j < 0) j = def.length();
163                 sd = def.substring(i, j);
164                 j++;
165
166                 if(direction.equals("horizontal")) {
167                     for(xx = x; (xx>=0) && (grid[xx][y].solution != '.'); xx--)
168                         definitions[xx][y][0] = sd;
169                     for(xx = x; (xx<gridWidth) && (grid[xx][y].solution != '.'); xx++) 
170                         definitions[xx][y][0] = sd;
171                 } else if(direction.equals("vertical")) {
172                     for(yy = y; (yy>=0) && (grid[x][yy].solution != '.'); yy--)
173                         definitions[x][yy][1] = sd;
174                     for(yy = y; (yy<gridHeight) && (grid[x][yy].solution != '.'); yy++)
175                         definitions[x][yy][1] = sd;
176                 } else System.out.println("Unknown orientation '" + direction + "'");
177             }
178         }
179
180         addKeyListener(this);
181         addMouseListener(this);
182     }
183
184     public void giveWord() {
185         int xx, yy;
186
187         int x = xCursor;
188         int y = yCursor;
189
190         if(cursorHorizontal) {
191             for(xx = x; (xx>=0) && (grid[xx][y].solution != '.'); xx--) {
192                 grid[xx][y].setFound(grid[xx][y].solution);
193                 grid[xx][y].lock(Color.red);
194             }
195           
196             for(xx = x; (xx<gridWidth) && (grid[xx][y].solution != '.'); xx++) {
197                 grid[xx][y].setFound(grid[xx][y].solution);
198                 grid[xx][y].lock(Color.red);
199             }
200         } else {
201             for(yy = y; (yy>=0) && (grid[x][yy].solution != '.'); yy--) {
202                 grid[x][yy].setFound(grid[x][yy].solution);
203                 grid[x][yy].lock(Color.red);
204             }
205
206             for(yy = y; (yy<gridHeight) && (grid[x][yy].solution != '.'); yy++) {
207                 grid[x][yy].setFound(grid[x][yy].solution);
208                 grid[x][yy].lock(Color.red);
209             }
210         }
211
212         nbFound = 0;
213         for(y = 0; y<gridHeight; y++) for(x = 0; x<gridWidth; x++)
214             if(grid[x][y].solution == grid[x][y].found) nbFound++;
215       
216         if(nbFound == nbLetters) {
217             for(y = 0; y<gridHeight; y++) for(x = 0; x<gridWidth; x++)
218                 grid[x][y].lock(Color.blue);
219         }
220
221         repaint();
222     }
223
224     public void checkWord() {
225         boolean ok;
226         int xmin, xmax, xx;
227         int ymin, ymax, yy;
228
229         int x = xCursor;
230         int y = yCursor;
231
232         if(cursorHorizontal) {
233             ok = true;
234             for(xmin = x; ok && (xmin>=0) && (grid[xmin][y].solution != '.'); xmin--)
235                 ok &= (grid[xmin][y].solution == grid[xmin][y].found);
236           
237             for(xmax = x; ok && (xmax<gridWidth) && (grid[xmax][y].solution != '.'); xmax++)
238                 ok &= (grid[xmax][y].solution == grid[xmax][y].found);
239           
240             xmin++; xmax--;
241           
242             if(ok) for(xx = xmin; xx<=xmax; xx++) grid[xx][y].lock(Color.green);
243
244         } else {
245             ok = true;
246             for(ymin = y; ok && (ymin>=0) && (grid[x][ymin].solution != '.'); ymin--)
247                 ok &= (grid[x][ymin].solution == grid[x][ymin].found);
248           
249             for(ymax = y; ok && (ymax<gridHeight) && (grid[x][ymax].solution != '.'); ymax++)
250                 ok &= (grid[x][ymax].solution == grid[x][ymax].found);
251           
252             ymin++; ymax--;
253           
254             if(ok) for(yy = ymin; yy<=ymax; yy++) grid[x][yy].lock(Color.green);
255         }
256
257         repaint();
258     }
259
260     public synchronized void drawSquare(Graphics g, int x, int y) {
261         int arrowMargin = 2;
262         int xe, ye, deltaX;
263         int xf[], yf[];
264         char tmp[];
265         tmp = new char[1];
266         xe = x*squareSize+xMargin; ye = y*squareSize+yMargin;
267         g.setFont(font);
268
269         if(grid[x][y].solution != '.') {
270             g.setColor(Color.white);
271             g.fillRect(xe, ye, squareSize, squareSize);
272
273             if(grid[x][y].holdCursor) {
274                 xf = new int[5]; yf = new int[5];
275                 g.setColor(Color.gray);
276                 if(cursorHorizontal) {
277                     xf[0] = xe + 1 + arrowMargin;
278                     yf[0] = ye + 1 + arrowMargin;
279                     xf[1] = xe + squareSize/2;
280                     yf[1] = ye + 1 + arrowMargin;
281                     xf[2] = xe + squareSize - 1 - arrowMargin;
282                     yf[2] = ye + squareSize/2;
283                     xf[3] = xe + squareSize/2;
284                     yf[3] = ye + squareSize - arrowMargin;
285                     xf[4] = xe + 1 + arrowMargin;
286                     yf[4] = ye + squareSize - arrowMargin;
287                     g.fillPolygon(xf, yf, 5);
288                 }
289                 else
290                     {
291                         xf[0] = xe + squareSize - arrowMargin;
292                         yf[0] = ye + 1 + arrowMargin;
293                         xf[1] = xe + squareSize - arrowMargin;
294                         yf[1] = ye + squareSize/2;
295                         xf[2] = xe + squareSize/2;  
296                         yf[2] = ye + squareSize - 1 - arrowMargin;
297                         xf[3] = xe + 1 + arrowMargin;
298                         yf[3] = ye + squareSize/2;
299                         xf[4] = xe + 1 + arrowMargin;
300                         yf[4] = ye + 1 + arrowMargin;
301                         g.fillPolygon(xf, yf, 5);
302                     }
303             }
304
305             g.setColor(Color.black);
306             g.drawRect(xe, ye, squareSize, squareSize);
307             tmp[0] = grid[x][y].found;
308             deltaX = (squareSize - fontMetrics.charWidth(tmp[0]))/2;
309
310             if(grid[x][y].locked) g.setColor(grid[x][y].color);
311             g.drawChars(tmp, 0, 1, xe + deltaX, ye+fontMetrics.getAscent());
312
313         } else {
314             g.setColor(Color.black);
315             g.fillRect(xe, ye, squareSize+1, squareSize+1);
316         }
317         grid[x][y].ajour = true;
318     }
319
320     public synchronized void update(Graphics g) {
321         int x, y;
322         for(y = 0; y<gridHeight; y++) for(x = 0; x<gridWidth; x++)
323             if(!grid[x][y].ajour) drawSquare(g, x, y);
324     }
325
326     public synchronized void paint(Graphics g) {
327         int x, y;
328         char tmp[];
329
330         g.setFont(smallFont);
331         g.setColor(Color.black);
332
333         tmp = new char[1];
334         for(y = 0; y<gridHeight; y++) {
335             tmp[0] = (char) (y + (int) 'a');
336             g.drawChars(tmp, 0, 1,
337                         xMargin - smallFontMetrics.charWidth(tmp[0]) - 3,
338                         y*squareSize + yMargin + smallFontMetrics.getAscent());
339         }
340
341         for(x = 0; x<gridWidth; x++)
342             g.drawString("" + (x+1),
343                          xMargin + x*squareSize + 3,
344                          yMargin - smallFontMetrics.getDescent() - 1);
345       
346         for(y = 0; y<gridHeight; y++) for(x = 0; x<gridWidth; x++)
347             drawSquare(g, x, y);
348     }
349
350     public void keyTyped(KeyEvent keyEvent) {}
351     public void keyReleased(KeyEvent keyEvent) {}
352
353     public void keyPressed(KeyEvent keyEvent) {
354         int x, y;
355         int code = keyEvent.getKeyCode();
356         char key = keyEvent.getKeyChar();
357
358         if(code == KeyEvent.VK_UP) {
359             y = yCursor-1;
360             while((y > 0) && (grid[xCursor][y].solution == '.')) y--;
361             if((y >= 0) && (grid[xCursor][y].solution != '.')) {
362                 grid[xCursor][yCursor].remCursor();
363                 yCursor = y;
364                 grid[xCursor][yCursor].putCursor();
365             }
366         }
367         else if(code == KeyEvent.VK_DOWN) {
368             y = yCursor+1;
369             while((y < gridHeight-1) && (grid[xCursor][y].solution == '.')) y++;
370             if((y < gridHeight) && (grid[xCursor][y].solution != '.')) {
371                 grid[xCursor][yCursor].remCursor();
372                 yCursor = y;
373                 grid[xCursor][yCursor].putCursor();
374             }
375         }
376         else if(code == KeyEvent.VK_LEFT) {
377             x = xCursor-1;
378             while((x > 0) && (grid[x][yCursor].solution == '.')) x--;
379             if((x >= 0) && (grid[x][yCursor].solution != '.')) {
380                 grid[xCursor][yCursor].remCursor();
381                 xCursor = x;
382                 grid[xCursor][yCursor].putCursor();
383             }
384         }
385         else if(code == KeyEvent.VK_RIGHT) {
386             x = xCursor+1;
387             while((x < gridWidth-1) && (grid[x][yCursor].solution == '.')) x++;
388             if((x < gridWidth) && (grid[x][yCursor].solution != '.')) {
389                 grid[xCursor][yCursor].remCursor();
390                 xCursor = x;
391                 grid[xCursor][yCursor].putCursor();
392             }
393         }
394         else if(code == Event.ENTER) {
395             cursorHorizontal = !cursorHorizontal;
396             grid[xCursor][yCursor].ajour = false;
397         }
398         else if(code == Event.BACK_SPACE) {
399             if((grid[xCursor][yCursor].found == ' ') || grid[xCursor][yCursor].locked) {
400                 if(cursorHorizontal) {
401                     if(xCursor > 0)
402                         if(grid[xCursor - 1][yCursor].solution != '.') {
403                             grid[xCursor][yCursor].remCursor();
404                             xCursor--;
405                             grid[xCursor][yCursor].putCursor();
406                         }
407                 } else {
408                     if(yCursor > 0)
409                         if(grid[xCursor][yCursor - 1].solution != '.') {
410                             grid[xCursor][yCursor].remCursor();
411                             yCursor--;
412                             grid[xCursor][yCursor].putCursor();
413                         }
414                 }
415             }
416
417             if(!grid[xCursor][yCursor].locked) {
418                 if(grid[xCursor][yCursor].found == grid[xCursor][yCursor].solution)
419                     nbFound--;
420                 grid[xCursor][yCursor].setFound(' ');
421             }
422         } else if(key == ' ') {
423             if(definitionArea != null) {
424                 if(cursorHorizontal) definitionArea.setText(definitions[xCursor][yCursor][0]);
425                 else                 definitionArea.setText(definitions[xCursor][yCursor][1]);
426             }
427         } else if((key >= 'a') && (key <= 'z')) {
428             if(!grid[xCursor][yCursor].locked) {
429                 if(grid[xCursor][yCursor].found == grid[xCursor][yCursor].solution)
430                     nbFound--;
431                 grid[xCursor][yCursor].setFound((char) key);
432                 if(grid[xCursor][yCursor].found == grid[xCursor][yCursor].solution)
433                     nbFound++;
434             }
435
436             if(cursorHorizontal) {
437                 if(xCursor < gridWidth-1)
438                     if(grid[xCursor + 1][yCursor].solution != '.') {
439                         grid[xCursor][yCursor].remCursor();
440                         xCursor++;
441                         grid[xCursor][yCursor].putCursor();
442                     }
443             } else {
444                 if(yCursor < gridHeight-1)
445                     if(grid[xCursor][yCursor + 1].solution != '.') {
446                         grid[xCursor][yCursor].remCursor();
447                         yCursor++;
448                         grid[xCursor][yCursor].putCursor();
449                     }
450             }
451
452             if(nbFound == nbLetters) {
453                 for(y = 0; y<gridHeight; y++) for(x = 0; x<gridWidth; x++)
454                     grid[x][y].lock(Color.blue);
455             }
456         }
457
458         repaint();
459     }
460
461     public void mousePressed(MouseEvent mouseEvent) {
462         int xc, yc;
463         xc = (mouseEvent.getX()-xMargin)/squareSize;
464         if(xc < 0) xc = 0;
465         if(xc >= gridWidth) xc = gridWidth-1;
466         yc = (mouseEvent.getY()-yMargin)/squareSize;
467         if(yc < 0) yc = 0;
468         if(yc >= gridHeight) yc = gridHeight-1;
469     
470         if(grid[xc][yc].solution != '.') {
471             grid[xCursor][yCursor].remCursor();
472             xCursor = xc; yCursor = yc;
473             grid[xCursor][yCursor].putCursor();
474         }
475     
476         repaint();
477     }
478
479     public void mouseClicked(MouseEvent mouseEvent) {}
480     public void mouseReleased(MouseEvent e) {}
481     public void mouseEntered(MouseEvent e) {}
482     public void mouseExited(MouseEvent e) {}
483
484     public Dimension getPreferredSize() {
485         return new Dimension(xMargin + gridWidth*squareSize + 1,
486                              yMargin + gridHeight*squareSize + 1);
487     }
488
489 }
490
491 public class Words extends Applet implements ActionListener {
492     Grid grid;
493     Button checkButton, giveButton;
494     TextArea definitionArea;
495
496     public void init() {
497         String fontName, s, definitionString;
498         int fontSize;
499
500         Font f, sf;
501         FontMetrics fm, sfm;
502         int w, h, wd, hd;
503
504         s = getParameter("bgcolor");
505         if(s != null)
506             setBackground(new Color(Integer.parseInt(s)));
507
508         fontName = getParameter("font");
509         if(fontName == null) fontName = "Courier";
510
511         s = getParameter("fontsize");
512         if(s == null) fontSize = 48; else fontSize = Integer.parseInt(s);
513
514         f = new Font(fontName, Font.PLAIN, fontSize);
515
516         fm = getGraphics().getFontMetrics(f);
517
518         fontName = getParameter("smallfont");
519         if(fontName == null) fontName = "Courier";
520
521         s = getParameter("smallfontsize");
522         if(s == null) fontSize = 20; else fontSize = Integer.parseInt(s);
523
524         sf = new Font(fontName, Font.ITALIC, fontSize);
525         sfm = getGraphics().getFontMetrics(sf);
526
527         s = getParameter("gridwidth");
528         if(s == null) w = 1; else w = Integer.parseInt(s);
529
530         s = getParameter("gridheight");
531         if(s == null) h = 1; else h = Integer.parseInt(s);
532
533         definitionString = null;
534
535         s = getParameter("definitions");
536
537         if(s != null) {
538             int i, j;
539             wd = -1; hd = -1;
540
541             // Find the width and the height
542             i = 0; j = s.indexOf('x', i);
543             wd = Integer.parseInt(s.substring(i, j));
544             i = j+1; j = s.indexOf('/', i);
545             hd = Integer.parseInt(s.substring(i, j));
546
547             i = j+1; definitionString = s.substring(i, s.length());
548
549             if((wd > 0) && (hd > 0)) {
550                 definitionArea = new TextArea("", hd, wd, TextArea.SCROLLBARS_NONE);
551                 definitionArea.setEditable(false);
552             }
553         }
554
555         s = getParameter("content"); if(s == null) s = ".";
556         grid = new Grid(f, sf, fm, sfm, w, h, s, definitionArea, definitionString);
557         add("north", grid);
558
559         s = getParameter("checkwordlabel");
560         if(s != null) {
561             checkButton = new Button(s);
562             checkButton.addActionListener(this);
563         }
564
565         s = getParameter("givewordlabel");
566         if(s != null) {
567             giveButton = new Button(s);
568             giveButton.addActionListener(this);
569         }
570
571         if((checkButton != null) || (giveButton != null)) {
572             Panel buttonPanel;
573             buttonPanel = new Panel();
574             if(checkButton != null) buttonPanel.add(checkButton);
575             if(giveButton != null) buttonPanel.add(giveButton);
576             if(definitionArea != null) add("south", definitionArea);
577             add("south", buttonPanel);
578         }
579     }
580
581     public void actionPerformed(ActionEvent event) {
582         Object source = event.getSource();
583         if(source == checkButton) grid.checkWord();
584         else if(source == giveButton) grid.giveWord();
585     }
586
587     public String getAppletInfo() {
588         return "Word applet v1.5\n" +
589             "Written and (c) Francois Fleuret, mail to <francois.fleuret@inria.fr>\n" + 
590             "Check http://www-rocq.inria.fr/~fleuret\n";
591     }
592     
593     public String[][] getParameterInfo() {
594         String chose[][] = {
595             { "bgcolor", "integer", "Background Color (optional)" },
596             { "font", "font name", "Grid character font name (optional)" },
597             { "fontsize", "integer", "Grid character font size (optional)" },
598             { "smallfont", "font name", "Row and column index font name (optional)" },
599             { "smallfontsize", "integer", "Row and column index font size (optional)" },
600             { "gridwidth", "integer", "Grid width" },
601             { "gridheight", "integer", "Grid height" },
602             { "content", "string", "Grid content" },
603             { "definitions", "string", "Definitions (optional)" },
604             { "checkwordlabel", "string", "Label for the 'check word' button (optional)" },
605             { "givewordlabel", "string", "Label for the 'give word' button (optional)" }
606         };
607
608         return chose;
609     }
610 }
611