2
* Copyright (C) 1998 Janne L�f <jlof@mail.student.oulu.fi>
4
* This library is free software; you can redistribute it and/or
5
* modify it under the terms of the GNU Library General Public
6
* License as published by the Free Software Foundation; either
7
* version 2 of the License, or (at your option) any later version.
9
* This library is distributed in the hope that it will be useful,
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
* Library General Public License for more details.
14
* You should have received a copy of the GNU Library General Public
15
* License along with this library; if not, write to the Free
16
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20
/* Zktor is a word that does not mean anything and is difficult to pronounce. */
21
/* I apologize for horrible coding. */
29
#include <gdk/gdkkeysyms.h>
30
#include <gtkgl/gtkglarea.h>
39
/* #define FULLSCREEN_MESA_3DFX /* uncomment this to get 3DFX acceleration */
41
#ifdef FULLSCREEN_MESA_3DFX
48
int state; /* zero state means dead */
58
GTimer *gtimer = NULL;
87
return (1.0*rand()/(RAND_MAX+1.0));
89
int collision(const Entity *a, const Entity *b)
91
if (a->state && b->state) {
92
float dx = a->pos_x - b->pos_x;
93
float dy = a->pos_y - b->pos_y;
94
float r = a->radius + b->radius;
95
if (dx*dx+dy*dy < r*r)
101
void gCircle(float radius, int points)
103
float a,step = 360.0/points;
104
for (a = 0; a < 360.0; a += step) {
105
float dx = -sin(a*M_PI/180);
106
float dy = cos(a*M_PI/180);
107
glVertex2f(dx*radius,dy*radius);
117
gtimer = g_timer_new();
118
g_timer_reset(gtimer);
120
game_time = g_timer_elapsed(gtimer, NULL);
121
game_tick = 1.0 / 60;
124
wave_time = game_time + 5; /* give 5 secs before start of waves */
125
vortex_time = game_time + 3; /* give 3 secs before 1st vortex */
135
player.timer = game_time;
144
for (i=0; i<sizeof(enemy)/sizeof(Entity); i++)
147
for (i=0; i<sizeof(vortex)/sizeof(Entity); i++)
150
for (i=0; i<sizeof(p_bullet)/sizeof(Entity); i++)
151
p_bullet[i].state = 0;
153
for (i=0; i<sizeof(e_bullet)/sizeof(Entity); i++)
154
e_bullet[i].state = 0;
159
double time_now,tick_now;
162
time_now = g_timer_elapsed(gtimer, NULL);
163
tick_now = time_now - game_time;
164
if (tick_now < 0.001) tick_now = 0.001;
165
if (tick_now > 0.2 ) tick_now = 0.2;
166
game_tick = (tick_now + 4*game_tick)/5; /* average */
167
game_time = time_now;
170
/* is it time for next wave? */
171
if (player.state && wave_time <= game_time) {
172
wave_time = game_time + 20; /* 20 second waves */
174
for (i=0; i<wave_cnt; i++) {
176
for (j=0; j<sizeof(enemy)/sizeof(Entity); j++) {
177
if (!enemy[j].state) {
178
enemy[j].radius = 50;
180
enemy[j].pos_x = rnd()*200 - 100;
181
enemy[j].pos_y = rnd()*200 - 100;
182
} while (collision(&enemy[j], &player));
184
enemy[j].timer = game_time;
187
enemy[j].dir = 360*rnd()-180;
201
/* turn to direction given by spin control */
202
player.dir += 180 * control_spin * game_tick;
203
while (player.dir > 180) player.dir -= 360;
204
while (player.dir <-180) player.dir += 360;
205
/* unit direction vector */
206
dx = -sin(player.dir*(M_PI/180));
207
dy = cos(player.dir*(M_PI/180));
208
/* accelerate if speed control is pressed */
209
player.vel_x += 50 * control_speed * dx * game_tick;
210
player.vel_y += 50 * control_speed * dy * game_tick;
212
player.pos_x += player.vel_x * game_tick;
213
player.pos_y += player.vel_y * game_tick;
214
/* collision to border */
215
if (player.pos_x < -100) {
219
if (player.pos_x > 100) {
223
if (player.pos_y < -100) {
227
if (player.pos_y > 100) {
231
/* if fire is pressed and 0.2 secs has elapsed since last bullet fired */
232
if (control_fire && (game_time - player.timer) > 0.2 ) {
233
for (i=0; i<sizeof(p_bullet)/sizeof(Entity); i++) {
234
if (!p_bullet[i].state) {
235
player.timer = game_time;
236
p_bullet[i].state = 1;
237
p_bullet[i].timer = game_time;
238
p_bullet[i].pos_x = player.pos_x + player.radius*dx;
239
p_bullet[i].pos_y = player.pos_y + player.radius*dy;
240
p_bullet[i].vel_x = player.vel_x + dx*100;
241
p_bullet[i].vel_y = player.vel_y + dy*100;
242
p_bullet[i].dir = player.dir;
243
p_bullet[i].radius= .5;
249
/* if speed control is pressed create more particles */
251
for (i=0; i<sizeof(particle)/sizeof(Entity); i++) {
252
if (!particle[i].state) {
253
float spread = rnd()*15 - 7.5;
254
particle[i].state = 1;
255
particle[i].timer = game_time + rnd()*.8 + .2;
256
particle[i].pos_x = player.pos_x - player.radius*dx;
257
particle[i].pos_y = player.pos_y - player.radius*dy;
258
particle[i].vel_x = player.vel_x - dx*25 - dy * spread;
259
particle[i].vel_y = player.vel_y - dy*25 + dx * spread;
260
particle[i].dir = player.dir;
261
particle[i].radius= .1;
269
/* enemies continue to chase player position even if player is dead
270
- make it center of screen if player is dead */
277
for (i=0; i<sizeof(enemy)/sizeof(Entity); i++) {
278
if (enemy[i].state) {
282
/* distance to player */
283
x = player.pos_x - enemy[i].pos_x;
284
y = player.pos_y - enemy[i].pos_y;
285
/* calculate signed angle to player */
286
a = enemy[i].dir + atan2(x,y) * 180/M_PI;
287
while (a > 180) a-=360;
288
while (a <-180) a+=360;
289
/* turn towards player */
291
enemy[i].dir += 90 * game_tick;
292
while (enemy[i].dir > 180) enemy[i].dir -= 360;
295
enemy[i].dir -= 90 * game_tick;
296
while (enemy[i].dir <-180) enemy[i].dir += 360;
298
/* unit direction vector */
299
dx = -sin(enemy[i].dir*(M_PI/180));
300
dy = cos(enemy[i].dir*(M_PI/180));
301
/* accelerate if player is ahead */
303
enemy[i].vel_x += 20*dx * game_tick;
304
enemy[i].vel_y += 20*dy * game_tick;
307
enemy[i].pos_x += enemy[i].vel_x * game_tick;
308
enemy[i].pos_y += enemy[i].vel_y * game_tick;
309
/* collision to border */
310
if (enemy[i].pos_x < -100) {
311
enemy[i].pos_x = -100;
314
if (enemy[i].pos_x > 100) {
315
enemy[i].pos_x = 100;
318
if (enemy[i].pos_y < -100) {
319
enemy[i].pos_y = -100;
322
if (enemy[i].pos_y > 100) {
323
enemy[i].pos_y = 100;
326
/* fire if player is alive and ahead */
327
if (player.state && (fabs(a) < 20) && (game_time - enemy[i].timer) > .6) {
328
for (j=0; j<sizeof(e_bullet)/sizeof(Entity); j++) {
329
if (!e_bullet[j].state) {
330
enemy[i].timer = game_time;
331
e_bullet[j].state = 1;
332
e_bullet[j].timer = game_time;
333
e_bullet[j].pos_x = enemy[i].pos_x + enemy[i].radius*dx;
334
e_bullet[j].pos_y = enemy[i].pos_y + enemy[i].radius*dy;
335
e_bullet[j].vel_x = enemy[i].vel_x + dx*60;
336
e_bullet[j].vel_y = enemy[i].vel_y + dy*60;
337
e_bullet[j].dir = enemy[i].dir;
338
e_bullet[j].radius= .5;
348
for (i=0; i<sizeof(vortex)/sizeof(Entity); i++) {
349
if (vortex[i].state) {
351
/* kill this vortex, time is up */
352
if (vortex[i].timer < game_time) {
357
vortex[i].dir += 60 * game_tick;
358
vortex[i].pos_x += vortex[i].vel_x * game_tick;
359
vortex[i].pos_y += vortex[i].vel_y * game_tick;
360
/* collision to border */
361
if (vortex[i].pos_x < -100) {
362
vortex[i].pos_x = -200 - vortex[i].pos_x;
363
vortex[i].vel_x = -vortex[i].vel_x;
365
if (vortex[i].pos_x > 100) {
366
vortex[i].pos_x = 200 - vortex[i].pos_x;
367
vortex[i].vel_x = -vortex[i].vel_x;
369
if (vortex[i].pos_y < -100) {
370
vortex[i].pos_y = -200 - vortex[i].pos_y;
371
vortex[i].vel_y = -vortex[i].vel_y;
373
if (vortex[i].pos_y > 100) {
374
vortex[i].pos_y = 200 - vortex[i].pos_y;
375
vortex[i].vel_y = -vortex[i].vel_y;
379
if (collision(&vortex[i], &player)) {
380
player.vel_x += (rnd()*500 - 250) * game_tick;
381
player.vel_y += (rnd()*500 - 250) * game_tick;
382
player.dir += (rnd()*180 - 90) * game_tick;
385
for (j=0; j<sizeof(enemy)/sizeof(Entity); j++) {
386
if (collision(&vortex[i], &enemy[j])) {
387
enemy[j].vel_x += (rnd()*500 - 250) * game_tick;
388
enemy[j].vel_y += (rnd()*500 - 250) * game_tick;
389
enemy[i].dir += (rnd()*180 - 90) * game_tick;
393
} else if (vortex_time < game_time) {
394
vortex_time = game_time + rnd()*10 + 5;
396
vortex[i].pos_x = 200*rnd()-100;
397
vortex[i].pos_y = 200*rnd()-100;
398
vortex[i].radius = rnd()*10 + 10;
399
} while (collision(&vortex[i], &player));
401
vortex[i].timer = game_time + rnd()*15 + 5;
402
do vortex[i].vel_x = rnd()*30 - 15; while (fabs(vortex[i].vel_x) < 10);
403
do vortex[i].vel_y = rnd()*30 - 15; while (fabs(vortex[i].vel_y) < 10);
410
for (i=0; i<sizeof(p_bullet)/sizeof(Entity); i++) {
411
if (p_bullet[i].state) {
414
p_bullet[i].pos_x += p_bullet[i].vel_x * game_tick;
415
p_bullet[i].pos_y += p_bullet[i].vel_y * game_tick;
416
/* collision to border kills bullet */
417
if (p_bullet[i].pos_x < -115 ||
418
p_bullet[i].pos_x > 115 ||
419
p_bullet[i].pos_y < -115 ||
420
p_bullet[i].pos_y > 115) {
421
p_bullet[i].state = 0;
423
/* collision to enemy. kills bullet and kills enemy */
424
/* and adds 100 to score */
425
for (j=0; j<sizeof(enemy)/sizeof(Entity); j++) {
426
if (collision(&p_bullet[i],&enemy[j])) {
428
if (score > highscore) highscore = score;
429
p_bullet[i].state = 0;
433
/* collision to vortex shakes bullet */
434
for (j=0; j<sizeof(vortex)/sizeof(Entity); j++) {
435
if (collision(&p_bullet[i], &vortex[j])) {
436
p_bullet[i].vel_x += (rnd()*600 - 300) * game_tick;
437
p_bullet[i].vel_y += (rnd()*600 - 300) * game_tick;
438
p_bullet[i].dir = rnd()*360-180;
445
for (i=0; i<sizeof(e_bullet)/sizeof(Entity); i++) {
446
if (e_bullet[i].state) {
449
e_bullet[i].pos_x += e_bullet[i].vel_x * game_tick;
450
e_bullet[i].pos_y += e_bullet[i].vel_y * game_tick;
451
/* collision to border kills bullet */
452
if (e_bullet[i].pos_x < -115 ||
453
e_bullet[i].pos_x > 115 ||
454
e_bullet[i].pos_y < -115 ||
455
e_bullet[i].pos_y > 115)
456
e_bullet[i].state = 0;
457
/* collision to player kills bullet and kills player */
458
if (collision(&e_bullet[i], &player)) {
459
e_bullet[i].state = 0;
462
/* collision to vortex shakes bullet */
463
for (j=0; j<sizeof(vortex)/sizeof(Entity); j++) {
464
if (collision(&p_bullet[i], &vortex[j])) {
465
e_bullet[i].vel_x += (rnd()*600 - 300) * game_tick;
466
e_bullet[i].vel_y += (rnd()*600 - 300) * game_tick;
467
e_bullet[i].dir = rnd()*360-180;
474
for (i=0; i<sizeof(particle)/sizeof(Entity); i++) {
475
if (particle[i].state) {
476
/* time elapsed, kill particle */
477
if (particle[i].timer < game_time) {
478
particle[i].state = 0;
482
particle[i].pos_x += particle[i].vel_x * game_tick;
483
particle[i].pos_y += particle[i].vel_y * game_tick;
495
glDisable(GL_LINE_SMOOTH);
496
glDisable(GL_POINT_SMOOTH);
499
glEnable(GL_LINE_SMOOTH);
500
glEnable(GL_POINT_SMOOTH);
502
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
507
glMatrixMode(GL_PROJECTION);
509
gluOrtho2D(-115,115,-115,115);
510
glMatrixMode(GL_MODELVIEW);
513
/* clear background */
514
glClearColor(0,0,0,1);
515
glClear(GL_COLOR_BUFFER_BIT);
517
/* frame around image */
519
glBegin(GL_LINE_LOOP);
520
glVertex2f(-110, 110);
521
glVertex2f( 110, 110);
522
glVertex2f( 110,-110);
523
glVertex2f(-110,-110);
527
glBegin(GL_LINE_LOOP);
528
glVertex2f(-105, 105);
529
glVertex2f( 105, 105);
530
glVertex2f( 105,-105);
531
glVertex2f(-105,-105);
535
glBegin(GL_LINE_LOOP);
536
glVertex2f(-100, 100);
537
glVertex2f( 100, 100);
538
glVertex2f( 100,-100);
539
glVertex2f(-100,-100);
546
glTranslatef(player.pos_x, player.pos_y, 0);
547
glRotatef(player.dir, 0,0,1);
550
glBegin(GL_LINE_LOOP);
557
glBegin(GL_LINE_STRIP);
570
for (i=0; i<sizeof(enemy)/sizeof(Entity); i++) {
571
if (enemy[i].state) {
573
glTranslatef(enemy[i].pos_x, enemy[i].pos_y, 0);
575
glRotatef(enemy[i].dir, 0,0,1);
578
glBegin(GL_LINE_STRIP);
586
glBegin(GL_LINE_LOOP);
597
for (i=0; i<sizeof(vortex)/sizeof(Entity); i++) {
598
if (vortex[i].state) {
601
glTranslatef(vortex[i].pos_x, vortex[i].pos_y, 0);
602
glRotatef(vortex[i].dir, 0,0,1);
605
glBegin(GL_LINE_LOOP);
606
gCircle(vortex[i].radius,6);
610
glBegin(GL_LINE_LOOP);
611
gCircle(vortex[i].radius*.7, 6);
615
glBegin(GL_LINE_LOOP);
616
gCircle(vortex[i].radius*.4, 6);
624
for (i=0; i<sizeof(p_bullet)/sizeof(Entity); i++) {
625
if (p_bullet[i].state) {
627
glTranslatef(p_bullet[i].pos_x, p_bullet[i].pos_y, 0);
628
glRotatef(p_bullet[i].dir, 0,0,1);
641
for (i=0; i<sizeof(e_bullet)/sizeof(Entity); i++) {
642
if (e_bullet[i].state) {
644
glTranslatef(e_bullet[i].pos_x, e_bullet[i].pos_y, 0);
645
glRotatef(e_bullet[i].dir, 0,0,1);
660
for (i=0; i<sizeof(particle)/sizeof(Entity); i++) {
661
if (particle[i].state)
662
glVertex2f(particle[i].pos_x, particle[i].pos_y);
669
g_snprintf(s, sizeof(s), "wave %d score %d highscore %d", wave_cnt, score, highscore);
672
glRasterPos2f(-90, 90);
673
glListBase(fontbase);
674
glCallLists(strlen(s), GL_UNSIGNED_BYTE, s);
681
/* --------------------------------------- */
684
#ifdef FULLSCREEN_MESA_3DFX
686
gint switch_fullscreen(GtkWidget *gl_area)
688
static GtkWidget *fullscreenwidget = NULL;
690
if (!fullscreenwidget)
692
/* Grab keyboard and pointer so that user does not wander off the game
693
window while in fullscreen mode.
695
if (gdk_keyboard_grab(gl_area->window, FALSE, GDK_CURRENT_TIME) == 0)
697
if (gdk_pointer_grab(gl_area->window, FALSE, 0, NULL, NULL, GDK_CURRENT_TIME) == 0)
699
gtk_widget_grab_focus(gl_area);
700
if (gtk_gl_area_make_current(GTK_GL_AREA(gl_area)))
702
if (XMesaSetFXmode((XMESA_FX_FULLSCREEN)))
704
fullscreenwidget = gl_area;
708
gdk_pointer_ungrab(GDK_CURRENT_TIME);
710
gdk_keyboard_ungrab(GDK_CURRENT_TIME);
715
if (fullscreenwidget == gl_area)
717
if (gtk_gl_area_make_current(GTK_GL_AREA(gl_area)))
718
XMesaSetFXmode(XMESA_FX_WINDOW);
720
gdk_keyboard_ungrab(GDK_CURRENT_TIME);
721
gdk_pointer_ungrab(GDK_CURRENT_TIME);
722
fullscreenwidget = NULL;
734
gint init(GtkWidget *widget)
736
/* OpenGL functions can be called only if makecurrent returns true */
737
if (gtk_gl_area_make_current(GTK_GL_AREA(widget))) {
743
glViewport(0,0, widget->allocation.width, widget->allocation.height);
746
/* generate font display lists */
747
font = gdk_font_load("-adobe-helvetica-medium-r-normal--*-120-*-*-*-*-*-*");
749
fontbase = glGenLists( 128 );
750
gdk_gl_use_gdk_font(font, 0, 128, fontbase);
751
gdk_font_unref(font);
759
/* When widget is exposed it's contents are redrawn. */
760
gint draw(GtkWidget *widget, GdkEventExpose *event)
762
/* Draw only last expose. */
763
if (event->count > 0)
766
if (gtk_gl_area_make_current(GTK_GL_AREA(widget)))
769
/* Swap backbuffer to front */
770
gtk_gl_area_swapbuffers(GTK_GL_AREA(widget));
775
/* When glarea widget size changes, viewport size is set to match the new size */
776
gint reshape(GtkWidget *widget, GdkEventConfigure *event)
778
/* OpenGL functions can be called only if make_current returns true */
779
if (gtk_gl_area_make_current(GTK_GL_AREA(widget)))
781
glViewport(0,0, widget->allocation.width, widget->allocation.height);
787
gint key_press_event(GtkWidget *widget, GdkEventKey *event)
789
switch (event->keyval) {
810
#ifdef FULLSCREEN_MESA_3DFX
812
switch_fullscreen(widget);
817
draw_fast = (!draw_fast);
820
/* prevent the default handler from being run */
821
gtk_signal_emit_stop_by_name(GTK_OBJECT(widget),"key_press_event");
825
gint key_release_event(GtkWidget *widget, GdkEventKey *event)
827
switch (event->keyval) {
829
if (control_spin == 1) control_spin = 0;
832
if (control_spin == -1) control_spin = 0;
842
/* prevent the default handler from being run */
843
gtk_signal_emit_stop_by_name(GTK_OBJECT(widget),"key_release_event");
847
gint animate(GtkWidget *glarea)
850
gtk_widget_draw(GTK_WIDGET(glarea), NULL);
855
int main(int argc, char **argv)
857
GtkWidget *window,*vbox,*logo,*glarea;
859
/* Attribute list for gtkglarea widget. Specifies a
860
list of Boolean attributes and enum/integer
861
attribute/value pairs. The last attribute must be
862
GDK_GL_NONE. See glXChooseVisual manpage for further
874
#ifdef FULLSCREEN_MESA_3DFX
875
setenv("MESA_GLX_FX", "", 1);
876
setenv("FX_GLIDE_NO_SPLASH", "", 1);
880
gtk_init(&argc, &argv);
882
/* Check if OpenGL (GLX extension) is supported. */
883
if (gdk_gl_query() == FALSE) {
884
g_print("OpenGL not supported\n");
888
/* Create new top level window. */
889
window = gtk_window_new( GTK_WINDOW_TOPLEVEL);
890
gtk_window_set_title(GTK_WINDOW(window), "Zktor");
892
/* Quit form main if got delete event */
893
gtk_signal_connect(GTK_OBJECT(window), "delete_event",
894
GTK_SIGNAL_FUNC(gtk_main_quit), NULL);
897
/* You should always delete gtk_gl_area widgets before exit or else
898
GLX contexts are left undeleted, this may cause problems (=core dump)
900
Destroy method of objects is not automatically called on exit.
901
You need to manually enable this feature. Do gtk_quit_add_destroy()
902
for all your top level windows unless you are certain that they get
903
destroy signal by other means.
905
gtk_quit_add_destroy(1, GTK_OBJECT(window));
908
vbox = GTK_WIDGET(gtk_vbox_new(FALSE, 0));
909
gtk_container_set_border_width(GTK_CONTAINER(vbox), 10);
912
logo = gtk_label_new("Zktor");
915
/* Create new OpenGL widget. */
916
glarea = GTK_WIDGET(gtk_gl_area_new(attrlist));
917
/* Events for widget must be set before X Window is created */
918
gtk_widget_set_events(GTK_WIDGET(glarea),
921
GDK_KEY_RELEASE_MASK);
922
/* set minimum size */
923
/* gtk_widget_set_usize(GTK_WIDGET(glarea), 200,200); */
924
/* set default size */
925
gtk_gl_area_size(GTK_GL_AREA(glarea), 640,400);
928
/* Connect signal handlers */
929
/* Redraw image when exposed. */
930
gtk_signal_connect(GTK_OBJECT(glarea), "expose_event",
931
GTK_SIGNAL_FUNC(draw), NULL);
932
/* When window is resized viewport needs to be resized also. */
933
gtk_signal_connect(GTK_OBJECT(glarea), "configure_event",
934
GTK_SIGNAL_FUNC(reshape), NULL);
935
/* Do initialization when widget has been realized. */
936
gtk_signal_connect(GTK_OBJECT(glarea), "realize",
937
GTK_SIGNAL_FUNC(init), NULL);
938
/* Capture keypress events */
939
gtk_signal_connect(GTK_OBJECT(glarea), "key_press_event",
940
GTK_SIGNAL_FUNC(key_press_event), NULL);
941
gtk_signal_connect(GTK_OBJECT(glarea), "key_release_event",
942
GTK_SIGNAL_FUNC(key_release_event), NULL);
944
/* construct widget hierarchy */
945
gtk_container_add(GTK_CONTAINER(window),GTK_WIDGET(vbox));
946
gtk_box_pack_start(GTK_BOX(vbox), logo, FALSE, FALSE, 0);
947
gtk_box_pack_start(GTK_BOX(vbox), glarea, TRUE, TRUE, 0);
951
/* show all widgets */
952
gtk_widget_show(GTK_WIDGET(glarea));
953
gtk_widget_show(GTK_WIDGET(logo));
954
gtk_widget_show(GTK_WIDGET(vbox));
955
gtk_widget_show(window);
957
/* set focus to glarea widget */
958
GTK_WIDGET_SET_FLAGS(glarea, GTK_CAN_FOCUS);
959
gtk_widget_grab_focus(GTK_WIDGET(glarea));
962
gtk_idle_add((GtkFunction)animate, glarea);