Add .gitignore
[ztornado.git] / tornado.inf
1 ! Z-Tornado - Two player weather action game
2 !
3 ! Tornado is copyright (C) 2000-2002  Oliver Feiler
4 ! http://www.lionking.org/~kiza/linux/tornado
5 ! Inform version copyright (c) 2003  Sophie Frühling (sfruehling@aon.at)
6
7 ! This program is free software; you can redistribute it and/or modify
8 ! it under the terms of the GNU General Public License as published by
9 ! the Free Software Foundation; either version 3 of the License, or
10 ! (at your option) any later version.
11 !
12 ! This program is distributed in the hope that it will be useful,
13 ! but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 ! GNU General Public License for more details.
16 !
17 ! You should have received a copy of the GNU General Public License
18 ! along with this program. If not, see <https://www.gnu.org/licenses/>.
19
20 Release 2;
21
22 ! Screen stuff
23 Global screen_height;
24 Global screen_width;
25 Global dialog_x; ! dialog box cursor for player input 
26 Global cloud_x;
27 ! Can be switched off in terps where colour doesn't work properly
28 ! (For all I know only useful in Unix Frotz 2.32, where timed input
29 ! doesn't work like it should either)
30 Global colour = 1;
31
32 ! Player stuff
33 ! 1 if current player, otherwise 0
34 Global player1;
35 Global player2;
36 Array player1_name -> 18;
37 Array player2_name -> 18;
38 Global player1_ai = 0;
39 Global player2_ai = 0;
40 Global player1_left = 100;
41 Global player2_left = 100;
42 ! Can handle scores up to 9999999, which should be more than enough
43 Array player1_score -> 7;
44 Array player2_score -> 7;
45
46 ! House stuff
47 ! Position relative to screen
48 Global house1_x;
49 Global house1_y;
50 Global house2_x;
51 Global house2_y;
52 ! How many spaces does the house initially contain?
53 Constant SPACES = 32;
54
55 ! Weather stuff
56 Global windspeed;
57 Global randseed = 0;
58 ! 80 x 24 grid, discounting 1 row for statusline and 4 for cloud
59 Array snow_world -> 1520;
60 ! Actual entries of snow_world
61 Global fields = 0;
62
63 ! Highscores
64 Array highscore_file string "ZTORNADO.HGH";
65 Array highscore_array -> 220;
66
67 ! Add degree character to Zcharacter table
68 Zcharacter table + '@{B0}';
69
70 Include "draw.inf";
71 Include "erwin.inf";
72 Include "scores.inf";
73 Include "weather.inf";
74
75 [ Main  input;
76   if (TestTerp() == 0) return;
77   DrawTitleScreen();
78   InitHighScores();
79   while ((input = Menu()) ~= 'Q') {
80     switch (input) {
81       'A':
82         About();
83       'C':
84         if (colour) colour = 0;
85         else colour = 1;
86       'I':
87         Instructions();
88       'P':
89         Game();
90     }
91   }
92   Quit();
93 ];
94
95 [ Menu  ch;
96   @erase_window -1;
97   print "^^^";
98   Banner();
99   print "----------------------^";
100   print "(A) About Z-Tornado^",
101         "(C) Toggle colour (currently ";
102         if (colour) print "on";
103         else print "off";
104   print ")^",
105         "(I) Instructions^",
106         "(P) Play Z-Tornado^",
107         "(Q) Quit^^";
108   while (1) {
109     ch = Read();
110     switch (ch) {
111       'A', 'C', 'I', 'P', 'Q':
112         return ch;
113     }
114   }
115 ];
116
117 [ Game; Init(); Tornado(); Bye(); ];
118
119 Array score_array->7;
120 [ Tornado  quitting tmp_score;
121   while (1) {
122     InitGame();
123     while (1) {
124       @erase_window 1;
125       DrawSnowWorld();
126       DrawCloud(0);
127       windspeed = random(11) - 6;
128       if (player1) {
129         player1 = 0; player2 = 1;
130         quitting = Play(player2_ai);
131       } else {
132         player2 = 0; player1 = 1;
133         quitting = Play(player1_ai);
134       }
135       if (player1_left <= 0) {
136         tmp_score = 100
137           - ((CountDestroyed(' ', house2_x, house2_y, 17, 10) - SPACES)*100)/138;
138         CopyArray(score_array, player2_score);
139         Multiply(player2_score, score_array, tmp_score);
140         break;
141       }
142       if (player2_left <= 0) {
143         tmp_score = 100
144           - ((CountDestroyed(' ', house1_x, house1_y, 17, 10) - SPACES)*100)/138;
145         CopyArray(score_array, player1_score);
146         Multiply(player1_score, score_array, tmp_score);
147         break;
148       }
149       if (quitting) return;
150     }
151     DrawStatusLine();
152     AddToHighScore(player1_name, player1_score);
153     AddToHighScore(player2_name, player2_score);
154     if (ContinueGame() == 'N')
155       return;
156   }
157 ];
158
159 [ Play ai  x input aim destroyed;
160   DrawStatusLine();
161   ! We have a human player
162   if (ai == 0) {
163     while (1) {
164       input = PlayerChoice();
165       if (input == 'Q')
166         rtrue;
167       ! lightning: player doesn't have to aim
168       ! tornado: we'll draw the tornado cloud before aiming
169       if (input == 'L' or 'T')
170         break;
171       if (input == 'H' or 'R' or 'S') {
172         aim = GetAim();
173         break;
174       }
175       if (input == 'C')
176         ShowCurrentScores();
177       else if (input == 'O')
178         ShowHighScores();
179     } 
180   } else { ! The computer chooses
181     if (player1)
182       input = ErwinChoice(player1_left);
183     else
184       input = ErwinChoice(player2_left);
185     aim = ErwinAim(input);
186   }
187   destroyed = 0;
188   switch (input) {
189     'H':
190       x = cloud_x + random(14) - 1;
191       destroyed = DrawWeather(x, windspeed + aim, ':', 2);
192     'R':
193       x = cloud_x + random(14) - 1;
194       destroyed = DrawWeather(x, windspeed + aim, '/', 1);
195     'S':
196       x = cloud_x + random(14) - 1;
197       DrawSnow(x, windspeed + aim);
198     'L':
199       destroyed = DrawLightning(cloud_x - 7 + random(25));
200     'T':
201       DrawTornadoCloud();
202       if (ai == 0) aim = GetAim();
203       else Pause(2); ! So the player can see the cloud for a while
204       destroyed = DrawTornado(cloud_x, windspeed + aim);
205   }
206   player2_left = 100
207     - ((CountDestroyed(' ', house2_x, house2_y, 17, 10)
208     + CountSnow(house2_x, house2_y, 17, 10) - SPACES)*100)/138;
209   player1_left = 100
210     - ((CountDestroyed(' ', house1_x, house1_y, 17, 10)
211     + CountSnow(house1_x, house1_y, 17, 10) - SPACES)*100)/138;
212   ! (You get points for destroying your own house, too)
213   if (player1) {
214     CopyArray(score_array, player1_score);
215     AddShort(player1_score, score_array, destroyed*destroyed);
216   } else {
217     CopyArray(score_array, player2_score);
218     AddShort(player2_score, score_array, destroyed*destroyed);
219   }
220   DrawStatusLine();
221   rfalse;
222 ];
223
224 [ PlayerChoice  ch;
225   DrawUserInterface();
226   DrawStatusLine();
227   while (1) {
228     ch = Read();
229     switch (ch) {
230       'S', 'H', 'T', 'Q', 'R', 'L', 'C', 'O':
231         ClearUserInterface();
232         DrawSnowWorld();
233         return ch;
234     }
235   }
236 ];
237
238 [ GetAim  num;
239   DrawDialogWindow();
240   DrawStatusLine();
241   @set_cursor 8 dialog_x;
242   print "Aim?";
243   @set_cursor 9 dialog_x;
244   num = ReadNumber();
245   @set_cursor screen_height 1;
246   ClearDialogWindow();
247   DrawSnowWorld();
248   return num;
249 ];
250
251 ! Get the position of x y in snow_world array
252 [ GetLoc x y;
253   if (x < 1 || x > screen_width || y < 5 || y > screen_height)
254     rfalse;
255   if (y == 5) x--;
256   return (y - 5)*screen_width + x;
257 ];
258
259 [ CountDestroyed ch x y wid hgt  i j n count;
260   for (i = x: i < x + wid: i++) {
261     for (j = y: j < y + hgt: j++) {
262        n = GetLoc(i, j);
263        if (snow_world->n == ch)
264          count++;
265     }
266   }
267   return count;
268 ];
269
270 [ CountSnow xx yy wid hgt;
271   return CountDestroyed('*', xx, yy, wid, hgt);
272 ];
273
274 [ ChangeWorld x y num  i;
275   for (i = 0: i < num: i++)
276     ChangeSnowWorld(' ', x + i, y);
277 ];
278
279 [ ChangeSnowWorld ch xx yy  x;
280   x = GetLoc(xx, yy);
281   if (x < fields)
282     snow_world->x = ch;
283 ];
284
285 ! ----------- Initialising and game ending stuff -------------
286
287 [ TestTerp;
288   screen_width = 0->33;
289   screen_height = 0->32;
290   if (screen_width < 64)
291     print "Your interpreter detected a screen width of only ",
292           screen_width, ".^";
293   if (screen_height < 24)
294     print "Your interpreter detected a screen height of only ",
295           screen_height, ".^";
296   if (screen_width < 64 || screen_height < 24) {
297     print "To play Tornado you need at least 64 x 24.^^";
298     rfalse;
299   }
300   if (screen_width > 80) screen_width = 80;
301   screen_height = 24;
302   if ((0->1) & 128 == 0) {
303     print "Your interpreter apparently doesn't support timed events.",
304           " This game should be pretty tiresome to play without, sorry.^";
305     Read();
306   }
307   if ($32-->0 == 0) {
308     print "Your interpreter doesn't obey the Z-Machine standard. Z-Tornado",
309           " will probably not work correctly on such an interpreter.^";
310     Read();
311   }
312 ];
313
314 [ Init  y;
315   y = 0->32;
316   dialog_x = screen_width/2 - 16;
317   @split_window y;
318   @set_window 1;
319   if (colour) @set_colour 9 2;
320   house1_x = 5;
321   house1_y = screen_height - 10;
322   house2_x = screen_width - 21;
323   house2_y = screen_height - 10;
324   fields = screen_height*screen_width - 5*screen_width;
325   InitSnowWorld();
326   @erase_window 1;
327   DrawSnowWorld();
328   DrawCloud(10);
329   player1_ai = 0;
330   player2_ai = 0;
331   GetNames();
332 ];
333
334 ! There might be some bug in there, because Windows Frotz 2002 doesn't draw
335 ! the dialog window correctly after asking for the first player's name
336 [ GetNames  i;
337   DrawDialogWindow();
338   for (i = 2: i < 17: i++) {
339     player1_name->i = ' ';
340     player2_name->i = ' ';
341   }
342   @set_cursor 8 dialog_x;
343   print "Please enter your name Player 1";
344   @set_cursor 9 dialog_x;
345   player1_name->0 = 15;
346   read player1_name 0;
347   if (player1_name->1 == 0) {
348     ("Computer").print_to_array(player1_name);
349     player1_ai = 1;
350   }
351   @set_cursor 8 dialog_x;
352   print "Please enter your name Player 2";
353   @set_cursor 9 dialog_x;
354   print "               ";
355   @set_cursor 9 dialog_x;
356   player2_name->0 = 15;
357   read player2_name 0;
358   if (player2_name->1 == 0) {
359     ("Computer").print_to_array(player2_name);
360     player2_ai = 1;
361   }
362   @set_cursor screen_height 1;
363 ];
364
365 [ InitGame;
366   @erase_window 1;
367   player1_left = 100;
368   player2_left = 100;
369   player1 = 0;
370   player2 = 1;
371   Nullify(player1_score);
372   Nullify(player2_score);
373   InitSnowWorld();
374   DrawSnowWorld();
375   DrawCloud(10);
376 ];
377
378 [ InitSnowWorld  i;
379   for (i = 0: i < fields: i++)
380     snow_world->i = ' ';
381   InitHouse(0);
382   InitHouse(1);
383 ];
384
385 Array building table
386   "          ___    "
387   "          |||    "
388   "  /-------'|`-@@92  "
389   "/'/////////////`@@92"
390   "|---------------|"
391   "|-,_____,--,__,-|"
392   "|-|__|__|--|++|-|"
393   "|-|__|__|--|o+|-|"
394   "|----------|++|-|"
395   "|__________|__|_|";
396
397 Array temp_array -> 20;
398 [ InitHouse nr  i j x y tmp;
399   if (nr) {
400     x = house1_x;
401     y = house1_y;
402   } else {
403     x = house2_x;
404     y = house2_y;
405   }
406   temp_array->0 = 17;
407   for (j = 1: j <= 10: j++) {
408     tmp = GetLoc(x, y + j - 1);
409     (building-->j).print_to_array(temp_array);
410     for (i = 2: i < 19: i++)
411       snow_world->tmp++ = temp_array->i;
412   }
413 ];
414   
415 [ ContinueGame  yesno;
416   DrawDialogWindow();
417   DrawWinningScreen();
418   yesno = Read();
419   while (yesno ~= 'Y' && yesno ~= 'N')
420     yesno = Read();
421   ClearDialogWindow();
422   return yesno;
423 ];
424
425 [ Bye  y;
426   @erase_window 1;
427   y = screen_height - 2;
428   @set_cursor y 1;
429   @set_colour 1 1;
430   style roman;
431   @erase_window -1;
432 ];
433
434 [ Quit  x;
435   @erase_window -1;
436   print "^^[Trying to save highscore file]^";
437   @save highscore_array 220 highscore_file -> x;
438   if (x == 0)
439     print "^[Couldn't save the highscore file, sorry.]^";
440   print "^^Thanks for playing Tornado!^^";
441 ];
442
443 ! ---------------- Low level routines ---------------------
444
445 ! Read in a character/digit from the keyboard
446 [ Read  x k;
447   if (randseed > 0)
448     @read_char 1 x;
449   else {
450     ! This should make the random function more random.
451     @read_char 1 1 SeedRand -> k; 
452     for (k = 0: k < randseed: k++)
453       random(1); 
454   }
455   ! Convert lower case to upper case so we only have to test
456   ! for half of the cases
457   if ('a' <= x && x <= 'z')
458     return x - ('a' - 'A');
459   return x;
460 ];
461
462 [ SeedRand; randseed++; rfalse; ];
463
464 ! Read in numbers from -99 to 999 (We really only need -9 to 9)
465 ! If the player doesn't type a number, or anything, we return 0.
466 Array num_array->6;
467 [ ReadNumber  x y neg tens sum;
468   num_array->0 = 3;
469   read num_array 0;
470   if (num_array->1 == 0)
471     return 0;
472   x = 2;
473   if (num_array->2 == '-') {
474     x++;
475     neg = 1;
476   }
477   for (sum = 0: x < num_array->1 + 2: x++) {
478     if (num_array->x >= '0' && num_array->x <= '9') {
479       for (tens = 1, y = num_array->1: y + 1 > x: y--)
480         tens = tens * 10;
481       sum = sum + (num_array->x - '0') * tens;
482     }
483     else break;
484   }
485   if (neg) sum = -sum;
486   return sum;
487 ];
488
489 ! Pauses for x/10 seconds
490 [ Pause x  t;
491   @read_char 1 x Interrupt -> t;
492 ];
493
494 [ Interrupt; rtrue; ];