]> bicyclesonthemoon.info Git - ott/bsta/blob - 2words.1.pl
2c1cca6fd2355b051b7ca970d9a8e87fe9315934
[ott/bsta] / 2words.1.pl
1 ###RUN_PERL: #!/usr/bin/perl
2
3 # /bsta/2words
4 # 2words is generated from 2words.1.pl.
5 #
6 # The wordgame interface
7 #
8 # Copyright (C) 2016 - 2017, 2023  Balthasar SzczepaƄski
9 #
10 # This program is free software: you can redistribute it and/or modify
11 # it under the terms of the GNU Affero General Public License as
12 # published by the Free Software Foundation, either version 3 of the
13 # License, or (at your option) any later version.
14 #
15 # This program is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 # GNU Affero General Public License for more details.
19 #
20 # You should have received a copy of the GNU Affero General Public License
21 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
22
23 use strict;
24 use utf8;
25 # use Encode::Locale ('decode_argv');
26 use Encode ('encode', 'decode');
27
28 ###PERL_LIB: use lib /botm/lib/bsta
29 use botm_common (
30         'read_data_file', 'write_data_file',
31         'join_path',
32         'merge_url',
33         'read_header_env',
34         'html_entity_encode_dec',
35         'url_query_decode'
36 );
37 use bsta_lib (
38         'STATE', 'INTF_STATE',
39         'fail_method', 'fail_content_type',
40         'print_html_start', 'print_html_end',
41         'print_html_head_start', 'print_html_head_end',
42         'print_html_body_start', 'print_html_body_end',
43         'write_index',
44         'get_remote_addr',
45         'merge_settings'
46 );
47 use  File::Copy;
48
49 ###PERL_PATH_SEPARATOR:     PATH_SEPARATOR     = /
50
51 ###PERL_CGI_PATH:           CGI_PATH           = /bsta/
52 ###PERL_CGI_2WORDS_PATH:    CGI_2WORDS_PATH    = /bsta/2words
53
54 ###PERL_DATA_PATH:          DATA_PATH          = /botm/data/bsta/
55 ###PERL_DATA_DEFAULT_PATH:  DATA_DEFAULT_PATH  = /botm/data/bsta/default
56 ###PERL_DATA_LIST_PATH:     DATA_LIST_PATH     = /botm/data/bsta/list
57 ###PERL_DATA_SETTINGS_PATH: DATA_SETTINGS_PATH = /botm/data/bsta/settings
58 ###PERL_DATA_STATE_PATH:    DATA_STATE_PATH    = /botm/data/bsta/state
59 ###PERL_DATA_STORY_PATH:    DATA_STORY_PATH    = /botm/data/bsta/story
60
61 ###PERL_WWW_PATH:           WWW_PATH           = /botm/www/1190/bsta/
62
63 ###PERL_WEBSITE_NAME:       WEBSITE_NAME       = Bicycles on the Moon
64
65 ###PERL_STORY_LENGTH:       STORY_LENGTH       = 16
66 ###PERL_PAGE_LENGTH:        PAGE_LENGTH        = 16
67 ###PERL_FIRSTPAGE_LENGTH:   FIRSTPAGE_LENGTH   = 4
68
69 binmode STDIN,  ':encoding(UTF-8)';
70 binmode STDOUT, ':encoding(UTF-8)';
71 binmode STDERR, ':encoding(UTF-8)';
72 # decode_argv();
73
74 my %http;
75 my %cgi;
76 my %story;
77 my %new_story;
78 my %settings;
79 my %state;
80 my %goto_list;
81
82 my $time = time();
83 srand ($time-$$);
84
85 my $method;
86 my $IP;
87 my $words;
88 my $color2;
89 my $last_IP;
90 my $story_id;
91 my $turn;
92 my $message;
93 my $first_letter;
94 my $second_letter;
95 my $last_letter;
96 my $intf_state;
97 my $intf_pass;
98 my $intf_pause;
99 my $intf_mode;
100 my $story_i_path;
101 my $story_file;
102 my $story_lock;
103 my @story_lines;
104 my $ong_state;
105 my $page;
106 my $cmd_clear;
107 my $cmd_clear_all;
108
109
110 delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
111 ###PERL_SET_PATH: $ENV{'PATH'} = /usr/local/bin:/usr/bin:/bin;
112
113 if ($ENV{'REQUEST_METHOD'} =~ /^(HEAD|GET|POST)$/) {
114         $method = $1;
115 }
116 else{
117         exit fail_method($ENV{'REQUEST_METHOD'}, 'GET, POST, HEAD');
118 }
119
120 %http = read_header_env(\%ENV);
121 %cgi = url_query_decode($ENV{'QUERY_STRING'});
122 if ($method eq 'POST') {
123         if ($http{'content-type'} eq 'application/x-www-form-urlencoded') {
124                 my %cgi_post=url_query_decode( <STDIN> );
125                 foreach my $ind (keys %cgi_post) {
126                         $cgi{$ind} = $cgi_post{$ind};
127                 }
128         }
129         # multipart not supported
130         else{
131                 exit fail_content_type($http{'content-type'}, $method);
132         }
133 }
134 $IP = get_remote_addr();
135 if ($ENV{'PATH_INFO'} =~ /^\/(.+)$/) {
136         $page=int($1);
137 }
138 else {
139         $page=0;
140 }
141 if ($cgi{'words'} ne '') {
142         $words=$cgi{'words'};
143 }
144
145 %settings = read_data_file(DATA_SETTINGS_PATH());
146 %state    = read_data_file(DATA_STATE_PATH());
147 $ong_state = int($state{'state'});
148 $cmd_clear     = $settings{'password'}.' clear';
149 $cmd_clear_all = $settings{'password'}.' clearall';
150
151 $story_lock=0;
152 if (open ($story_file,"+<:encoding(UTF-8)",DATA_STORY_PATH())){
153         $story_lock=1;
154         if (flock($story_file,2)) {
155                 $story_lock=2;
156         }
157         %story = read_data_file($story_file);
158         
159         if ($story{'lastip'} =~ /^.+$/) {
160                 $last_IP=$&;
161         }
162         else {
163                 $last_IP='0.0.0.0';
164         }
165         
166         $last_letter = lc($story{'letter'});
167         $story_id   = int($story{'id'});
168         $intf_pass  = int($story{'pass'});
169         $intf_state = int($story{'state'});
170         $intf_mode  = $intf_state & INTF_STATE->{'mode'};
171         $intf_pause = $intf_state & INTF_STATE->{'||'};
172         
173         if ($IP ne $last_IP) {
174                 $turn = 1;
175         }
176         else {
177                 $turn = 0;
178         }
179         
180         if (
181                 ($words eq $cmd_clear) ||
182                 ($words eq $cmd_clear_all) ||
183                 ($intf_state < 0)
184         ) {
185                 if (
186                         ($words eq $cmd_clear_all) ||
187                         ($intf_state < -1)
188                 ) {
189                         $story{'id'} = 0;
190                 }
191                 $story{'content'} = '';
192                 $story{'lastip' } = '0.0.0.0';
193                 $story{'letter' } = '';
194                 $story{'pass'   } = 0;
195                 $story{'state'  } = INTF_STATE->{'X'};
196                 $turn = 0;
197                 if ($ong_state == STATE->{'inactive'}) {
198                         write_index(
199                                 \%state,
200                                 \%settings,
201                                 $story{'pass'},
202                                 $story{'state'},
203                                 0 # pause
204                         );
205                 }
206                 write_data_file($story_file, '', '', \%story);
207         }
208         
209         if ($words ne '') {
210                 if (!$turn) {
211                         $message = "It's not your turn.";
212                 }
213                 # TODO: consider allowing non-ASCII letters in words.
214                 # (not very important in English language)
215                 elsif ($words =~ /^([!"\(\),\.:;\?][ \t]*)?([A-Za-z][A-Za-z'\-]*[A-Za-z']?)([!"\(\),\.:;\? \t][ \t]*)([A-Za-z][A-Za-z'\-]*[A-Za-z']?)([!"\(\),\.:;\?]?[ \t]*)$/) {
216                         # we have 2 words
217                         $first_letter  = lc(substr($2, 0, 1));
218                         $second_letter = lc(substr($4, 0, 1));
219                         if (
220                                 ($first_letter ne $last_letter) &&
221                                 ($last_letter ne '')
222                         ) {
223                                 $message = 'The first word must start with '.uc($last_letter).'.';
224                         }
225                         elsif ($first_letter eq $second_letter) {
226                                 $message = 'The second word can\'t also start with '.uc($first_letter).'.';
227                         }
228                         else {
229                                 # words are valid
230                                 # update state
231                                 $story{'content'} = $story{'content'} . $words."\n";
232                                 $turn = 0;
233                                 $story{'lastip'} = $IP;
234                                 $story{'letter'} = $second_letter;
235                                 
236                                 if ($cgi{'next'} ne '') {
237                                         # start next game
238                                         if (split(/\r?\n/,$story{'content'}) >= (STORY_LENGTH-1)) {
239                                                 # store finished game
240                                                 $story_i_path = DATA_STORY_PATH.$story_id;
241                                                 write_data_file($story_i_path, '', '', \%story);
242                                                 # init new game
243                                                 $new_story{'id'     } = $story_id + 1;
244                                                 $new_story{'letter' } = '';
245                                                 $new_story{'lastip' } = $IP;
246                                                 $new_story{'content'} = '';
247                                                 $new_story{'pass'   } = 0;
248                                                 $new_story{'state'  } = INTF_STATE->{'X'};
249                                                 # reset hidden interface
250                                                 $intf_state = INTF_STATE->{'X'};
251                                                 $intf_pass = 0;
252                                                 $intf_mode = INTF_STATE->{'X'};
253                                                 $intf_pause= 0;
254                                                 if($ong_state == STATE->{'inactive'}) {
255                                                         # ONG not activated yet; reset index
256                                                         write_index(
257                                                                 \%state,
258                                                                 \%settings,
259                                                                 $intf_pass,
260                                                                 $intf_mode,
261                                                                 $intf_pause
262                                                         );
263                                                 }
264                                                 # save new game
265                                                 write_data_file($story_file, '', '', \%new_story);
266                                         }
267                                         else {
268                                                 $message = 'To early to finish this wordgame.';
269                                                 write_data_file($story_file, '', '', \%story);
270                                         }
271                                 }
272                                 else {
273                                         # continue the game
274                                         if ($intf_pass == 1) {
275                                                 # hidden interface was already active; deactivate
276                                                 $intf_pass = 2;
277                                                 $story{'pass'} = 2;
278                                                 if($ong_state == STATE->{'inactive'}) {
279                                                         write_index(
280                                                                 \%state,
281                                                                 \%settings,
282                                                                 $intf_pass,
283                                                                 $intf_mode,
284                                                                 $intf_pause
285                                                         );
286                                                 }
287                                         }
288                                         elsif(lc($2).' '.lc($4) eq $settings{'unlock'}) {
289                                                 # correct password for the hidden interface!
290                                                 if ($intf_pass != 0) {
291                                                         $message = 'The password has already been used in this story.';
292                                                 }
293                                                 elsif ($ong_state != STATE->{'inactive'}) {
294                                                         # ONG already active, nothing to do here
295                                                         $message = "???";
296                                                 }
297                                                 else {
298                                                         # ready to activate?
299                                                         my $frame_data_path;
300                                                         my %frame_data;
301                                                         my %default;
302                                                         my $frame_file;
303                                                         my $in_path;
304                                                         my $out_path;
305                                                         my $ext;
306                                                         
307                                                         # prepare to ONG frame 0!
308                                                         
309                                                         $frame_data_path = join_path(PATH_SEPARATOR(), DATA_PATH(), 0);
310                                                         %frame_data = read_data_file($frame_data_path);
311                                                         %default    = read_data_file(DATA_DEFAULT_PATH());
312                                                         
313                                                         $ext = (defined($frame_data{'ext'})) ?
314                                                                 $frame_data{'ext'} :
315                                                                 $default{'ext'};
316                                                         
317                                                         $frame_file = sprintf($settings{'frame'}, 0, $ext);
318                                                         $in_path  = join_path(PATH_SEPARATOR(), DATA_PATH(), $frame_file);;
319                                                         $out_path = join_path(PATH_SEPARATOR(), WWW_PATH(),  $frame_file);
320                                                         
321                                                         # set ONG time of frame 0
322                                                         # NOTE: might get overwritten later if ONG not launched
323                                                         $frame_data{'ongtime'} = $time;
324                                                         $frame_data{'timer'} = 0;
325                                                         write_data_file($frame_data_path, '', '', \%frame_data);
326                                                         
327                                                         # update the GOTO list with frame 0
328                                                         $goto_list{'title-0'  } = $frame_data{'title'};
329                                                         $goto_list{'ongtime-0'} = $frame_data{'ongtime'};
330                                                         write_data_file(DATA_LIST_PATH(), '', '', \%goto_list);
331                                                         
332                                                         if(copy ($in_path, $out_path)) {
333                                                                 # new state of hidden interface
334                                                                 $intf_pass = 1;
335                                                                 $intf_state = INTF_STATE->{'X'};
336                                                                 $intf_mode  = INTF_STATE->{'X'};
337                                                                 $intf_pause = 0;
338                                                                 $story{'pass'} = 1;
339                                                                 $story{'state'} = INTF_STATE->{'X'};
340                                                                 write_index(
341                                                                         \%state,
342                                                                         \%settings,
343                                                                         $intf_pass,
344                                                                         $intf_mode,
345                                                                         $intf_pause
346                                                                 );
347                                                         }
348                                                 }
349                                         }
350                                         write_data_file($story_file, '', '', \%story);
351                                 }
352                         }
353                 }
354                 else {
355                         $message = 'Please, two words, not more, not less (some punctuation is allowed).';
356                 }
357         }
358         elsif (
359                 ($cgi{'s'} ne '') &&
360                 ($intf_pass == 1) &&
361                 ($ong_state == STATE->{'inactive'})
362         ) {
363                 $intf_state = int($cgi{'s'}) & INTF_STATE->{'mask'};
364                 $intf_mode  = $intf_state & INTF_STATE->{'mode'};
365                 $intf_pause = $intf_state & INTF_STATE->{'||'};
366                 $story{'state'} = $intf_state;
367                 write_index(
368                         \%state,
369                         \%settings,
370                         $intf_pass,
371                         $intf_mode,
372                         $intf_pause
373                 );
374                 write_data_file($story_file, '', '', \%story);
375         }
376         @story_lines = split(/\r?\n/, $story{'content'});
377         if(@story_lines & 1) {
378                 $turn = !$turn;
379         }
380         
381         close($story_file);
382 }
383
384 print "Content-type: text/html\n\n";
385 if($method eq 'HEAD') {
386         exit;
387 }
388
389 my $max_page = int(($story_id - FIRSTPAGE_LENGTH - 1) / PAGE_LENGTH) + 1;
390 my $newer_available = ($page > 0);
391 my $older_available = ($page < $max_page);
392 my $show_intf = ($intf_pass == 1) && ($ong_state == STATE->{'inactive'});
393 my $id_start = 
394         $story_id-1 -(
395                 ($page == 0)    ? 0 : (
396                         (($page-1) * PAGE_LENGTH ) + FIRSTPAGE_LENGTH
397                 )
398         );
399 my $id_stop = $story_id-1 - (($page*PAGE_LENGTH) + FIRSTPAGE_LENGTH);
400 if ($id_stop < 0) {
401         $id_stop = -1;
402 }
403
404 my $bsta_url = CGI_PATH;
405 my $twowords_url = CGI_2WORDS_PATH;
406 my $newer_url;
407 my $older_url;
408 my $oldest_url;
409 my $newest_url = merge_url(
410         {'path' => $twowords_url},
411         {'path' => 0}
412 );
413 if ($newer_available) {
414         $newer_url = merge_url(
415                 {'path' => $twowords_url},
416                 {'path' => $page-1}
417         );
418 }
419 if ($older_available) {
420         $older_url = merge_url(
421                 {'path' => $twowords_url},
422                 {'path' => $page+1}
423         );
424         $oldest_url = merge_url(
425                 {'path' => $twowords_url},
426                 {'path' => $max_page}
427         );
428 }
429 my $button_4_url = merge_url(
430         {'path' => $twowords_url},
431         {'query' => {'s' => (INTF_STATE->{'>'} | $intf_pause)}}
432 );
433 my $button_3_url = merge_url(
434         {'path' => $twowords_url},
435         {'query' => {'s' => (INTF_STATE->{'<<'} | $intf_pause)}}
436 );
437 my $button_2_url = merge_url(
438         {'path' => $twowords_url},
439         {'query' => {'s' => (INTF_STATE->{'>>'} | $intf_pause)}}
440 );
441 my $button_1_url = merge_url(
442         {'path' => $twowords_url},
443         {'query' => {'s' => INTF_STATE->{'X'}}}
444 );
445 my $button_0_url = merge_url(
446         {'path' => $twowords_url},
447         {'query' => {'s' => ($intf_pause ? $intf_mode : ($intf_mode | INTF_STATE->{'||'}))}}
448 );
449 my $button_5_img = merge_url(
450         {'path' => CGI_PATH()},
451         {'path' => 'intf-20.gif'}
452 );
453 my $button_4_img = merge_url(
454         {'path' => CGI_PATH()},
455         {'path' => 'intf-10'.(($intf_mode == INTF_STATE->{'>'}) ? '_' : '').'.gif'}
456 );
457 my $button_3_img = merge_url(
458         {'path' => CGI_PATH()},
459         {'path' => 'intf-08'.(($intf_mode == INTF_STATE->{'<<'}) ? '_' : '').'.gif'}
460 );
461 my $button_2_img = merge_url(
462         {'path' => CGI_PATH()},
463         {'path' => 'intf-04'.(($intf_mode == INTF_STATE->{'>>'}) ? '_' : '').'.gif'}
464 );
465 my $button_1_img = merge_url(
466         {'path' => CGI_PATH()},
467         {'path' => 'intf-02.gif'}
468 );
469 my $button_0_img = merge_url(
470         {'path' => CGI_PATH()},
471         {'path' => 'intf-01'.($intf_pause ? '_' : '').'.gif'}
472 );
473 my $intf_img_id = '';
474 if ($intf_state == INTF_STATE->{'>'}) {
475         $intf_img_id = '_10'
476 }
477 elsif ($intf_mode == INTF_STATE->{'<<'}) {
478         $intf_img_id = '_08'
479 }
480 elsif ($intf_mode == INTF_STATE->{'>>'}) {
481         $intf_img_id = '_04'
482 }
483 my $intf_img = merge_url(
484         {'path' => CGI_PATH()},
485         {'path' => 'intf-00'.$intf_img_id.'.gif'}
486 );
487
488 my $_bsta_url     = html_entity_encode_dec($bsta_url     , 1);
489 my $_twowords_url = html_entity_encode_dec($twowords_url , 1);
490 my $_newest_url   = html_entity_encode_dec($newest_url   , 1);
491 my $_newer_url    = html_entity_encode_dec($newer_url    , 1);
492 my $_older_url    = html_entity_encode_dec($older_url    , 1);
493 my $_oldest_url   = html_entity_encode_dec($oldest_url   , 1);
494 my $_button_4_url = html_entity_encode_dec($button_4_url , 1);
495 my $_button_3_url = html_entity_encode_dec($button_3_url , 1);
496 my $_button_2_url = html_entity_encode_dec($button_2_url , 1);
497 my $_button_1_url = html_entity_encode_dec($button_1_url , 1);
498 my $_button_0_url = html_entity_encode_dec($button_0_url , 1);
499 my $_button_5_img = html_entity_encode_dec($button_5_img , 1);
500 my $_button_4_img = html_entity_encode_dec($button_4_img , 1);
501 my $_button_3_img = html_entity_encode_dec($button_3_img , 1);
502 my $_button_2_img = html_entity_encode_dec($button_2_img , 1);
503 my $_button_1_img = html_entity_encode_dec($button_1_img , 1);
504 my $_button_0_img = html_entity_encode_dec($button_0_img , 1);
505 my $_intf_img     = html_entity_encode_dec($intf_img     , 1);
506 my $_message      = html_entity_encode_dec($message      , 1);
507 my $_website_name = html_entity_encode_dec(WEBSITE_NAME(), 1);
508
509 print_html_start(\*STDOUT);
510 print_html_head_start(\*STDOUT);
511
512
513 print '  <title>Two words &bull; '.$_website_name.'</title>'."\n";
514 print '  <link rel="start" href="'.$_oldest_url.'">'."\n";
515 if ($older_available) {
516         print '  <link rel="prev" href="'.$_older_url.'">'."\n";
517 }
518 if ($newer_available) {
519         print '  <link rel="next" href="'.$_newer_url.'">'."\n";
520 }
521
522 print_html_head_end(\*STDOUT);
523 print_html_body_start(\*STDOUT);
524
525 print '   <div id="inst" class="ins">'."\n";
526
527 print '    <div id="title">'."\n";
528 print '     <h1 id="titletext">Two words</h1>'."\n";
529 print '    </div>'."\n";
530
531 if ($page == 0) {
532         print '    <div id="storypuzzle">'."\n";
533         for (my $i = 0; $i < @story_lines; ++$i) {
534                 print '     <span class="'.($turn ? 'ni':'br').'">'.html_entity_encode_dec($story_lines[$i], 1).'</span>'."\n";
535                 $turn = !$turn;
536         }
537         print '    </div>'."\n";
538
539         print '    <div id="command">'."\n";
540         if ($message ne '') {
541                 print '     <span class="br">'.$_message.'</span>'."\n";
542         }
543         
544         if ($turn) {
545                 print '     <form method="post" action="'.$_twowords_url.'">'."\n";
546                 if ($message eq '') {
547                         if ($story{"content"} eq '') {
548                                 print '      Two words, please:<br>'."\n";
549                         }
550                         else {
551                                 print '      Please continue, two words:<br>'."\n";
552                         }
553                 }
554                 print '      <input class="intx" type="text" name="words">'."\n";
555                 print '      <input class="inbt" type="submit" value="enter">'."\n";
556                 if(@story_lines >= (STORY_LENGTH-1)) {
557                         print '      <input class="inbt" type="submit" name="next" value="enter and then start a new one">'."\n";
558                 }
559                 print '     </form>'."\n";
560         }
561         else {
562                 if ($message eq '') {
563                         print '     Wait for it.'."\n";
564                 }
565         }
566         print '    </div>'."\n";
567 }
568 elsif ($message ne '') {
569         print '    <div id="command">'."\n";
570         print '     <span class="br">'.$_message.'</span>'."\n";
571         print '    </div>'."\n";
572 }
573 print '   </div>'."\n";
574
575 if ($show_intf) {
576         print '   <div id="framespace">'."\n";
577         print '    <table id="intftable" cellspacing="0" cellpadding="0">'."\n";
578         print '     <tr class="intf">'."\n";
579         print '      <td colspan="6" class="intf"><img src="'.$_intf_img.'" alt="" class="intf"></td>'."\n";
580         print '     </tr>'."\n";
581         
582         print '     <tr class="intf">'."\n";
583         print '      <td class="intf"><img src="'.$_button_5_img.'" alt="o" class="intf"></td>'."\n";
584         print '      <td class="intf"><a href="'.$_button_4_url.'"><img src="'.$_button_4_img.'" class="intf" alt="&gt;"></a></td>'."\n";
585         print '      <td class="intf"><a href="'.$_button_3_url.'"><img src="'.$_button_3_img.'" class="intf" alt="&lt;&lt;"></a></td>'."\n";
586         print '      <td class="intf"><a href="'.$_button_2_url.'"><img src="'.$_button_2_img.'" class="intf" alt="&gt;&gt;"></a></td>'."\n";
587         print '      <td class="intf"><a href="'.$_button_1_url.'"><img src="'.$_button_1_img.'" class="intf" alt="^"></a></td>'."\n";
588         print '      <td class="intf"><a href="'.$_button_0_url.'"><img src="'.$_button_0_img.'" class="intf" alt="||"></a></td>'."\n";
589         print '     </tr>'."\n";
590         print '    </table>'."\n";
591         print '   </div>'."\n";
592 }
593
594 print '   <div id="insb" class="ins">'."\n";
595
596 print '    <div id="undertext">'."\n";
597 for (my $i = $id_start; $i > $id_stop; --$i) {
598         $story_i_path = DATA_STORY_PATH.$i;
599         %new_story = read_data_file($story_i_path);
600         print '     <p class="'.(($i&1)?'br':'ni').'" id="s'.$i.'">'.html_entity_encode_dec($new_story{'content'}).'</p>'."\n";
601 }
602 print '    </div>'."\n";
603
604 print '    <div id="underlinks">'."\n";
605 print '     <a href="'.$_bsta_url.'">BSTA</a> |'."\n";
606 print '     <a href="'.$_twowords_url.'">Once again</a>';
607 if ($older_available) {
608         print ' |'."\n";
609         print '     <a href="'.$_older_url.'">Before</a>';
610 }
611 if ($newer_available) {
612         print ' |'."\n";
613         print '     <a href="'.$newer_url.'">Unbefore</a>';
614 }
615 if ($older_available) {
616         print ' |'."\n";
617         print '<a href="'.$_oldest_url.'">Initially</a>';
618 }
619 if($turn) {
620         print ' |'."\n";
621         print '     (Entering words here is irreversible. Your actions might be remembered forever. So please be reasonable.)';
622 }
623 print "\n";
624 print '    </div>'."\n";
625
626 print '   </div>'."\n";
627
628 print_html_body_end(\*STDOUT);
629 print_html_end(\*STDOUT);