ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/kgsueme/bin/kgsueme
Revision: 1.6
Committed: Thu May 29 03:11:56 2003 UTC (21 years ago) by elmex
Branch: MAIN
Changes since 1.5: +96 -12 lines
Log Message:
Added GnomeCanvas for board graphics

File Contents

# Content
1 #!/usr/bin/perl -I../lib/
2
3 #use PApp::Util qw(dumpval); # debug only
4
5 use Gtk;
6 use Gtk::Gdk;
7 use Gtk;
8 #use Gtk::Gdk::ImlibImage;
9 use Gnome;
10 init Gnome "kgsueme";
11
12
13 use KGS::Protocol;
14 use KGS::Listener::Debug;
15
16 use IO::Socket::INET;
17
18 use Errno;
19
20 init Gtk;
21
22 $HACK = 1; # do NEVER enable. ;)
23
24 our $config;
25
26 {
27 use Storable ();
28 use Scalar::Util ();
29
30 my $staterc = "$ENV{HOME}/.kgsueme";
31
32 my $state = -r $staterc ? Storable::retrieve($staterc) : {};
33 my @widgets;
34
35 $config = $state->{config} ||= {};
36
37 # grr... more gtk+ brokenness
38 my %get = (
39 hpane_position => sub { ($_[0]->children)[0]->allocation->[2] },
40 vpane_position => sub { ($_[0]->children)[0]->allocation->[3] },
41 window_size => sub { [ @{$_[0]->allocation}[2,3] ] },
42 #window_pos => sub { die PApp::Util::dumpval [ $_[0]->get_root_origin ] },
43 clist_column_widths => sub {
44 $_[0]{column_widths};
45 },
46 );
47
48 my %set = (
49 hpane_position => sub { $_[0]->set_position($_[1]) },
50 vpane_position => sub { $_[0]->set_position($_[1]) },
51 window_size => sub { $_[0]->set_default_size(@{$_[1]}) },
52 #window_pos => sub { $_[0]->set_uposition(@{$_[1]}) if @{$_[1]} },
53 clist_column_widths => sub {
54 my ($w, $v) = @_;
55 $v->[$_] && $w->set_column_width($_, $v->[$_]) for 0..$#$v;
56 $w->{column_widths} = $v;
57 $w->signal_connect(resize_column => sub { $v->[$_[1]] = $_[2]; });
58 },
59 );
60
61 sub state {
62 my ($widget, $class, $instance, %attr) = @_;
63
64 while (my ($k, $v) = each %attr) {
65 my ($set, $get) = $k =~ /=/ ? split /=/, $k : ($k, $k);
66 $v = $state->{$class}{"*"}{$get} if exists $state->{$class}{"*"}{$get};
67 $v = $state->{$class}{$instance}{$get} if exists $state->{$class}{$instance}{$get};
68 $set{$get} ? $set{$get}->($widget, $v) : $widget->set($set => $v);
69 }
70
71 $widget = [$widget, $class, $instance, \%attr];
72 Scalar::Util::weaken $widget->[0];
73
74 @widgets = (grep $_->[0], @widgets, $widget);
75 }
76
77 sub save_state {
78 for (@widgets) {
79 if ($_->[0]) {
80 my ($widget, $class, $instance, $attr) = @$_;
81 while (my ($k, $v) = each %$attr) {
82 my ($set, $get) = $k =~ /=/ ? split /=/, $k : ($k, $k);
83 $v = $get{$get} ? $get{$get}->($widget) : $widget->get($get);
84
85 $state->{$class}{"*"}{$get} = $v;
86 $state->{$class}{$instance}{$get} = $v;
87 }
88 }
89 ::status("save_state", "layout saved");
90 }
91
92 Storable::nstore($state, $staterc);
93 }
94 }
95
96 # make a clist unselectable
97 sub clist_autosort {
98 my $w = shift;
99 my ($c, $o) = (-1);
100 for (0..$w->columns-1) {
101 $w->signal_connect(click_column => sub {
102 if ($_[1] != $c) {
103 $c = $_[1];
104 $o = 0;
105 } else {
106 $o = !$o;
107 }
108 $w->set_sort_column($c);
109 $w->set_sort_type($o ? "descending" : "ascending");
110 $w->sort;
111 });
112 }
113
114 }
115
116 {
117 my $main = new kgsueme;
118
119 my %context_id;
120
121 sub status {
122 my ($type, $text) = @_;
123
124 $main->{status}->pop($context_id{$type}) if $context_id{$type};
125 $main->{status}->push($context_id{$type} ||= $main->{status}->get_context_id($type), $text) if $text;
126 }
127 }
128
129 main Gtk;
130
131 #############################################################################
132
133 package kgsueme;
134
135 use base KGS::Listener;
136
137 sub new {
138 my $self = shift;
139 $self = $self->SUPER::new(@_);
140
141 $self->{conn} = new KGS::Protocol;
142
143 KGS::Listener::Debug->new->listen($self->{conn}); #d# debug only :)
144
145 $self->listen($self->{conn});
146
147 $self->{roomlist} = new roomlist conn => $self->{conn};
148
149 $self->{window} = new Gtk::Window 'toplevel';
150 $self->{window}->set_title('kgsueme');
151 ::state $self->{window}, "main::window", undef, window_size => [400, 100];
152 $self->{window}->signal_connect(delete_event => sub { main_quit Gtk });
153
154 $self->{window}->add(my $vbox = new Gtk::VBox);
155
156 $vbox->pack_start(($buttonbox = new Gtk::HButtonBox), 0, 1, 0);
157 $buttonbox->set_spacing(0);
158
159 my $button = sub {
160 $buttonbox->add(my $button = new Gtk::Button $_[0]);
161 signal_connect $button clicked => $_[1];
162 };
163
164 $button->("Login", sub { $self->login; });
165 $button->("Roomlist", sub { $self->{roomlist}->show; });
166 $button->("Save Config & Layout", sub { ::save_state });
167 $button->("Quit", sub { main_quit Gtk });
168
169 $vbox->pack_start((my $hbox = new Gtk::HBox), 0, 1, 0);
170
171 $hbox->add(new Gtk::Label "Login");
172
173 $hbox->add($self->{login} = new_with_max_length Gtk::Entry 12);
174 $self->{login}->set_text($::config->{login});
175
176 if ($::HACK) {
177 $self->{login}->signal_connect(activate => sub {
178 $self->{conn}{name} = $self->{login}->get_text;
179 });
180 }
181
182 $hbox->add(new Gtk::Label "Password");
183 $hbox->add($self->{password} = new Gtk::Entry);
184 $self->{password}->set_visibility(0);
185
186 $vbox->pack_start(($self->{status} = new Gtk::Statusbar), 0, 1, 0);
187
188 $self->{window}->show_all;
189
190 $self;
191 }
192
193 sub login {
194 my ($self) = @_;
195
196 $self->{conn}->disconnect;
197
198 # initialize new socket and connection
199 my $sock = new IO::Socket::INET PeerHost => "kgs.kiseido.com", PeerPort => "2379"
200 or die;
201
202 $sock->blocking(1);
203 $self->{conn}->handshake($sock);
204 $sock->blocking(0);
205
206 my $input; $input = input_add Gtk::Gdk fileno $sock, "read", sub {
207 # this is dorked
208 my $buf;
209 if (0 >= sysread $sock, $buf, 16384
210 and !$!{EINTR} and !$!{EAGAIN}) {
211 input_remove Gtk::Gdk $input;
212 $self->event_disconnect;
213 }
214 $self->{conn}->feed_data($buf);
215 };
216
217 # now login
218 $self->{conn}->login($self->{login}->get_text, $self->{password}->get_text);
219 }
220
221 sub inject_login {
222 my ($self, $msg) = @_;
223
224 ::status("login", "logged in as '$self->{conn}{name}' with status '$msg->{result}'");
225 $::config->{login} = $self->{conn}{name};
226
227 if ($msg->{success}) {
228 warn "hiya\n";
229 for (keys %{$::config->{rooms}}) {
230 warn "hiya $_\n";
231 $self->{roomlist}->join_room($_);
232 }
233 }
234
235 warn PApp::Util::dumpval($::config);
236 }
237
238 sub event_disconnect { }
239
240 #############################################################################
241
242 package roomlist;
243
244 use base KGS::Listener::Roomlist;
245
246 sub new {
247 my $self = shift;
248 $self = $self->SUPER::new(@_);
249
250 $self->listen($self->{conn});
251
252 $self->{window} = new Gtk::Window 'toplevel';
253 $self->{window}->set_title('KGS Rooms');
254 ::state $self->{window}, "roomlist::window", undef, window_size => [400, 200];
255
256 $self->{window}->signal_connect(delete_event => sub { $self->{window}->hide });
257
258 $self->{window}->add(my $vbox = new Gtk::VBox);
259
260 $vbox->pack_start((my $sw = new Gtk::ScrolledWindow), 1, 1, 0);
261 $sw->set_policy("automatic", "always");
262
263 $sw->add($self->{roomlist} = new_with_titles Gtk::CList "Group", "Room Name", "Users", "Games", "Flags", "Channel");
264 $self->{roomlist}->set_selection_mode('multiple');
265 ::clist_autosort $self->{roomlist};
266 ::state $self->{roomlist}, "roomlist::roomlist", undef, clist_column_widths => [20, 200];
267
268 $self->{roomlist}->signal_connect(select_row => sub {
269 my $room = $self->{roomlist}->get_row_data($_[1])
270 or return;
271 $self->{roomlist}->unselect_all;
272 $self->join_room($room->{channel});
273 });
274
275 $self;
276 }
277
278 sub join_room {
279 my ($self, $channel) = @_;
280
281 $self->{room}{$channel} ||= room->new(channel => $channel, conn => $self->{conn}, users => {});
282 $self->{room}{$channel}->join;
283 }
284
285 sub show {
286 my ($self, $msg) = @_;
287
288 $self->msg(list_rooms => group => $_) for 0..5; # fetch all room names (should not!)
289 $self->{window}->show_all;
290 }
291
292 sub event_update {
293 my ($self) = @_;
294
295 $self->{event_update} ||= Gtk->timeout_add(200, sub {
296 my $l = $self->{roomlist};
297
298 $l->freeze;
299 my $pos = $l->get_vadjustment->get_value;
300 $l->clear;
301
302 my $row = 0;
303 for (values %{$self->{rooms}}) {
304 $l->append($_->{group}, $_->{name}, $_->{users}, $_->{games}, $_->{flags}, $_->{channel});
305 $l->set_row_data($row++, $_);
306 }
307 $l->sort;
308 $l->get_vadjustment->set_value($pos);
309 $l->thaw;
310
311 delete $self->{event_update};
312 0;
313 });
314 }
315
316 #############################################################################
317
318 package room;
319
320 use base KGS::Listener::Room;
321
322 sub new {
323 my $self = shift;
324 $self = $self->SUPER::new(@_);
325
326 $self->listen($self->{conn});
327
328 $self->{window} = new Gtk::Window 'toplevel';
329 $self->{window}->set_title("KGS Room $self->{name}");
330 ::state $self->{window}, "room::window", $self->{name}, window_size => [600, 400];
331
332 $self->{window}->signal_connect(delete_event => sub { $self->part });
333
334 $self->{window}->add(my $hpane = new Gtk::HPaned);
335 ::state $hpane, "room::hpane", $self->{name}, hpane_position => 200;
336
337 $hpane->add(my $vpane = new Gtk::VPaned);
338 ::state $vpane, "room::vpane", $self->{name}, vpane_position => 200;
339
340 $vpane->add(my $sw = new Gtk::ScrolledWindow);
341 $sw->set_policy("automatic", "always");
342
343 $sw->add($self->{gamelist} = new_with_titles Gtk::CList "T", "Black", "White", "Rules", "Notes");
344 ::clist_autosort $self->{gamelist};
345 ::state $self->{gamelist}, "room::gamelist", $self->{name}, clist_column_widths => [20, 120, 120, 120];
346
347 $self->{gamelist}->signal_connect(select_row => sub {
348 my $game = $self->{gamelist}->get_row_data($_[1])
349 or return;
350 $self->{game}{$game->{channel}} ||= new game %$game, conn => $self->{conn};
351 $self->{game}{$game->{channel}}->join;
352 $self->{gamelist}->unselect_all;
353 });
354
355 $vpane->add(my $vbox = new Gtk::VBox);
356
357 $vbox->pack_start((my $sw = new Gtk::ScrolledWindow), 1, 1, 0);
358 $sw->set_policy("automatic", "always");
359
360 $sw->add($self->{text} = new Gtk::Text);
361
362 $vbox->pack_start(($self->{entry} = new Gtk::Entry), 0, 1, 0);
363 $self->{entry}->signal_connect(activate => sub {
364 my $text = $self->{entry}->get_text;
365 $self->say($text) if $text =~ /\S/;
366 $self->{entry}->set_text("");
367 });
368
369 $hpane->add(my $sw = new Gtk::ScrolledWindow);
370 $sw->set_policy("automatic", "always");
371
372 $sw->add($self->{userlist} = new_with_titles Gtk::CList "User", "Rank", "Flags");
373 ::clist_autosort $self->{userlist};
374 ::state $self->{userlist}, "room::userlist", $self->{name}, clist_column_widths => [120, 30];
375
376 $self;
377 }
378
379 sub event_update {
380 my ($self) = @_;
381
382 $self->{event_update} ||= Gtk->timeout_add(200, sub {
383 my $l = $self->{userlist};
384
385 $l->freeze;
386 my $pos = $l->get_vadjustment->get_value;
387 $l->clear;
388
389 my $row = 0;
390 for (values %{$self->{users}}) {
391 $l->append($_->{name});
392 $l->set_row_data($row++, $_);
393 }
394 $l->sort;
395 $l->get_vadjustment->set_value($pos);
396 $l->thaw;
397
398 delete $self->{event_update};
399 0;
400 });
401 }
402
403 sub event_update_games {
404 my ($self) = @_;
405
406 $self->{event_update_games} ||= Gtk->timeout_add(200, sub {
407 my $l = $self->{gamelist};
408
409 $l->freeze;
410 my $pos = $l->get_vadjustment->get_value;
411 $l->clear;
412
413 my $row = 0;
414 for (values %{$self->{games}}) {
415 $l->append($_->type, $_->user0, $_->user1, $_->rules, $_->notes);
416 $l->set_row_data($row++, $_);
417 }
418 $l->sort;
419 $l->get_vadjustment->set_value($pos);
420 $l->thaw;
421
422 delete $self->{event_update_games};
423 0;
424 });
425 }
426
427 sub join {
428 my ($self) = @_;
429 $self->SUPER::join;
430
431 $self->{window}->show_all;
432 }
433
434 sub part {
435 my ($self) = @_;
436 $self->SUPER::part;
437
438 delete $::config->{rooms}{$self->{channel}};
439 $self->{window}->hide_all;
440 $self->event_update;
441 $self->event_update_games;
442 }
443
444 sub event_join {
445 my ($self) = @_;
446 $self->SUPER::event_join;
447
448 $::config->{rooms}{$self->{channel}} = 1;
449 }
450
451 sub event_update_roominfo {
452 my ($self) = @_;
453
454 $self->{text}->insert(undef, undef, undef, "$self->{owner}: $self->{description}\n\n");
455 }
456
457 sub inject_msg_room {
458 my ($self, $msg) = @_;
459 return unless $self->{channel} == $msg->{channel};
460
461 $self->{text}->insert(undef, undef, undef, "$msg->{name}: $msg->{message}\n");
462 }
463
464 #############################################################################
465
466 package game;
467
468 use base KGS::Listener::Game;
469 use base KGS::Game;
470
471 sub new {
472 my $self = shift;
473 $self = $self->SUPER::new(@_);
474
475 $self->listen($self->{conn});
476
477 $self->{window} = new Gtk::Window 'toplevel';
478 $self->{window}->set_title("KGS Game ".$self->user0." ".$self->user1);
479 ::state $self->{window}, "game::window", undef, window_size => [600, 500];
480
481 $self->{window}->signal_connect(delete_event => sub { $self->part });
482
483 $self->{window}->add(my $hpane = new Gtk::HPaned);
484 ::state $hpane, "game::hpane", undef, hpane_position => 500;
485
486 $hpane->add(my $canvas = new Gnome::Canvas);
487 $self->{canvas} = $canvas;
488 # $canvas->set_pixels_per_unit(300);
489 $canvas->set_scroll_region(0,0,400,400);
490 $self->{board} = [[]];
491 # $canvas->style->bg('normal', $canvas->style->white);
492 {
493 my $croot = $canvas->root;
494 my $cgroup = $croot->new($croot, "Gnome::CanvasGroup");
495 my $w = 400;
496 my $pad = 26;
497 my $box = $cgroup->new($cgroup,"Gnome::CanvasRect",
498 x1 => 0, x2 => $w,
499 y1 => 0, y2 => $w,
500 outline_color => "green",
501 fill_color => "green",
502 width_pixels => 2,
503 );
504 my $box2 = $cgroup->new($cgroup,"Gnome::CanvasRect",
505 x1 => $pad, x2 => $w - $pad,
506 y1 => $pad, y2 => $w - $pad,
507 outline_color => "brown",
508 fill_color => "brown",
509 width_pixels => 2,
510 );
511 my @lines;
512 my $x1 = $pad * 2; # == my $y1 = 30;
513 my $x2 = $w - ($pad * 2); # == my $y2 = 270;
514 my $w = $x2 - $x1;
515 my $s = $self->{size} - 1;
516
517 for (my $i = 0; $i <= $s; $i++) { # one more iteration for the finishing lines
518 push @lines,
519 $cgroup->new($cgroup,"Gnome::CanvasLine",
520 points => [ $x1, $x1 + ($w/$s)*$i, $x1 + $w, $x1 + ($w/$s)*$i ],
521 fill_color => "darkbrown",
522 width_pixels => 1);
523 push @lines,
524 $cgroup->new($cgroup,"Gnome::CanvasLine",
525 points => [ $x1 + ($w/$s)*$i, $x1, $x1 + ($w/$s)*$i, $x1 + $w ],
526 fill_color => "darkbrown",
527 width_pixels => 1);
528 }
529 $self->{board_gfx}->{lines} = \@lines;
530 my $stones = [[]];
531 my $wf = ($w/$s);
532 for (my $x = 0; $x < $self->{size}; $x++) {
533 for (my $y = 0; $y < $self->{size}; $y++) {
534 $stones->[$x]->[$y]->[0] = # black stone
535 $cgroup->new($cgroup,"Gnome::CanvasEllipse",
536 x1 => $x1 + $wf*$x - ($wf/2.2), x2 => $x1 + $wf*$x + ($wf/2),
537 y1 => $x1 + $wf*$y - ($wf/2.2), y2 => $x1 + $wf*$y + ($wf/2),
538 fill_color => "black");
539 $stones->[$x]->[$y]->[0]->hide;
540 $stones->[$x]->[$y]->[1] = # black stone
541 $cgroup->new($cgroup,"Gnome::CanvasEllipse",
542 x1 => $x1 + $wf*$x - ($wf/2.2), x2 => $x1 + $wf*$x + ($wf/2),
543 y1 => $x1 + $wf*$y - ($wf/2.2), y2 => $x1 + $wf*$y + ($wf/2),
544 fill_color => "white");
545 $stones->[$x]->[$y]->[1]->hide;
546 }
547 }
548 $self->{board_gfx}->{stones} = $stones;
549
550 # smooth => 1,
551 # spline_steps => 50
552 # $line->hide;
553 }
554 $canvas->show();
555
556
557 $hpane->add(my $vpane = new Gtk::VPaned);
558 ::state $vpane, "game", $self->{name}, vpane_position => 80;
559
560 $vpane->add(my $sw = new Gtk::ScrolledWindow);
561 $sw->set_policy("automatic", "always");
562
563 $sw->add($self->{userlist} = new_with_titles Gtk::CList "User", "Rank", "Flags");
564 ::clist_autosort $self->{userlist};
565 ::state $self->{userlist}, "room::userlist", $self->{name}, clist_column_widths => [120, 30];
566
567 $vpane->add(my $vbox = new Gtk::VBox);
568
569 $vbox->pack_start((my $sw = new Gtk::ScrolledWindow), 1, 1, 0);
570 $sw->set_policy("automatic", "always");
571
572 $sw->add($self->{text} = new Gtk::Text);
573
574 $vbox->pack_start(($self->{entry} = new Gtk::Entry), 0, 1, 0);
575 $self->{entry}->signal_connect(activate => sub {
576 my $text = $self->{entry}->get_text;
577 # add message
578 $self->{entry}->set_text("");
579 });
580
581 $self;
582 }
583
584 sub event_update {
585 my ($self) = @_;
586
587 $self->{event_update} ||= Gtk->timeout_add(200, sub {
588 my $l = $self->{userlist};
589
590 $l->freeze;
591 my $pos = $l->get_vadjustment->get_value;
592 $l->clear;
593
594 my $row = 0;
595 for (values %{$self->{users}}) {
596 $l->append($_->{name});
597 $l->set_row_data($row++, $_);
598 }
599 $l->sort;
600 $l->get_vadjustment->set_value($pos);
601 $l->thaw;
602
603 delete $self->{event_update};
604 0;
605 });
606 }
607
608 sub join {
609 my ($self) = @_;
610 $self->SUPER::join;
611
612 $self->{window}->show_all;
613 }
614
615 sub part {
616 my ($self) = @_;
617 $self->SUPER::part;
618
619 $self->{window}->hide_all;
620 $self->event_update;
621 }
622
623 sub event_update_tree {
624 my ($self) = @_;
625
626 # if (not defined $self->{board_pm}) {
627 # $self->{board_pm} = new Gtk::Gdk::Pixmap ($self->{board}->window, 100, 100, -1);
628 # $self->{board_pm}->draw_rectangle($self->{board}->style->white_gc, 1, 0, 0, 100, 100);
629 # }
630 # my $red = $self->{board}->window->get_colormap->color_alloc( { red => 65000, green => 0, blue => 0 } );
631 # my $red_gc = new Gtk::Gdk::GC ( $self->{board}->window );
632 # $red_gc->set_foreground( $red );
633 #
634 # my $px = $self->{board_pm};
635
636 for my $x (0 .. $self->{size} - 1) {
637 for my $y (0 .. $self->{size} - 1) {
638 # $self->{board}[$x][$y] = 0;
639 $self->{board_gfx}->{stones}->[$x]->[$y]->[0]->hide;
640 $self->{board_gfx}->{stones}->[$x]->[$y]->[1]->hide;
641 }
642 }
643
644 for (0..$self->{node}) {
645 while (my ($k, $v) = each %{$self->{tree}[$_]}) {
646 if ($k eq "move7") {
647 if ($v->[1] < 255) {
648 # $self->{board_gfx}->{stones}[$x][$y]->[0]->show;
649 ($v->[0] == 0) &&
650 $self->{board_gfx}->{stones}->[$v->[1]]->[$v->[2]]->[0]->show;
651 ($v->[0] == 1) &&
652 $self->{board_gfx}->{stones}->[$v->[1]]->[$v->[2]]->[1]->show;
653 # $self->{board}[$v->[1]][$v->[2]] = ($v->[0] + 1); # ("X", "O", "-")
654 # 0 1 2 (+1)
655 }
656 }
657 }
658 }
659 }
660
661 package KGS::Game::Board;
662 package KGS::Game::Node;
663 package KGS::Game::Tree;
664
665 1;
666
667
668