ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/kgsueme/kgsueme/board.pl
Revision: 1.2
Committed: Mon Jun 23 01:14:21 2003 UTC (20 years, 11 months ago) by pcg
Content type: text/plain
Branch: MAIN
Changes since 1.1: +28 -16 lines
Log Message:
*** empty log message ***

File Contents

# Content
1 package Gtk2::GoBoard;
2
3 # my try at a real widget, needs the realobjects branch from Glib-CVS
4
5 use Glib;
6
7 use KGS::Constants;
8 use KGS::Game::Board;
9
10 use POSIX qw(ceil);
11
12 register Glib::Type Gtk2::DrawingArea, __PACKAGE__,
13 signals => {},
14 properties => {};
15
16 sub new {
17 my ($self, %arg) = @_;
18 $self = Glib::Object::new $self;
19 while (my ($k, $v) = each %arg) {
20 $self->{$k} = $v
21 }
22 $self;
23 }
24
25 sub INSTANCE_INIT {
26 warn "instance_init @_\n";
27 my $self = shift;
28
29 $self->double_buffered (0);
30
31 $self->signal_connect(configure_event => \&configure_event);
32 }
33
34 sub size_allocate {
35 my ($self, $alloc) = @_;
36
37 my ($w, $h) = ($alloc->width, $alloc->height);
38
39 my $s = $self->{width} = $w;
40 warn "ALLOC($w,$h,$s)\n";#d#
41
42 1;
43 }
44
45 sub configure_event {
46 my ($self, $event) = @_;
47 warn "configure @_\n";#d#
48
49 delete $self->{stack};
50
51 my $s = $self->{width} = $self->allocation->width;
52 $self->{backgroundpb} = $self->draw_board ($s);
53
54 $self->{backgroundpm} = new Gtk2::Gdk::Pixmap $self->window, $self->{width}, $self->{width}, -1;
55 $self->{backgroundpm}->draw_pixbuf ($self->style->white_gc,
56 $self->{backgroundpb},
57 0, 0, 0, 0, $self->{width}, $self->{width},
58 "normal", 0, 0);
59 $self->window->set_back_pixmap ($self->{backgroundpm}, 0);
60
61 $self->repaint_board (delete $self->{board}, 0);
62 $self->window->clear_area (0, 0, $self->{width}, $self->{width});
63
64 1;
65 }
66
67 sub new_pixbuf {
68 my ($w, $h, $alpha, $fill) = @_;
69
70 my $pixbuf = new Gtk2::Gdk::Pixbuf 'rgb', $alpha, 8, $w, $h;
71 $pixbuf->fill ($fill) if defined $fill;
72
73 $pixbuf;
74 }
75
76 sub scale_pixbuf {
77 my ($src, $w, $h, $mode, $alpha) = @_;
78
79 my $dst = new_pixbuf $w, $h, $alpha;
80
81 $src->scale(
82 $dst, 0, 0, $w, $h, 0, 0,
83 $w / $src->get_width, $h / $src->get_height,
84 $mode,
85 );
86
87 $dst;
88 }
89
90 # create a stack of stones
91 sub create_stack {
92 my ($self, $mark, $size, $rand) = @_;
93
94 my $shadow = $size * 0.05;
95
96 my $c = \$self->{stack}{$mark};
97 unless ($$c) {
98 for my $stone ($mark & (MARK_W | MARK_GRAY_W) ? @::white_img : @::black_img) {
99 my $base = new_pixbuf $size + $shadow, $size + $shadow, 1, 0x00000000;
100
101 # zeroeth the shadow
102 if ($mark & (MARK_B | MARK_W)) {
103 # the -0.5's are a mystery to me
104 $::shadow_img->composite (
105 $base, $shadow, $shadow, $size, $size, $shadow - 0.5, $shadow - 0.5,
106 $size / $::shadow_img->get_width, $size / $::shadow_img->get_height,
107 $::config->{speed} ? 'tiles' : 'bilinear', 192
108 );
109 }
110
111 # first the big stones (handicap stones could be different)
112 for ([MARK_B, $mark & MARK_MOVE ? 255 : 255],
113 [MARK_W, $mark & MARK_MOVE ? 255 : 255],
114 [MARK_GRAY_B, 128],
115 [MARK_GRAY_W, 128]) {
116 my ($mask, $alpha) = @$_;
117 if ($mark & $mask) {
118 $stone->composite (
119 $base, 0, 0, $size, $size, 0, 0,
120 $size / $stone->get_width, $size / $stone->get_height,
121 $::config->{speed} ? 'tiles' : 'bilinear', $alpha
122 );
123 }
124 }
125
126 # then the small stones
127 for ([MARK_SMALL_B, $::black_img[$rand % @::black_img]],
128 [MARK_SMALL_W, $::white_img[$rand % @::white_img]]) {
129 my ($mask, $img) = @$_;
130 if ($mark & $mask) {
131 $img->composite (
132 $base, (int ($size / 4)) x2, (ceil ($size / 2 + 1)) x2, ($size / 4) x2,
133 $size / $img->get_width / 2, $size / $img->get_height / 2,
134 $::config->{speed} ? 'tiles' : 'bilinear', 224
135 );
136 }
137 }
138
139 # and lastly any markers
140 my $dark_bg = ! ! ($mark & (MARK_B | MARK_GRAY_B));
141
142 for ([MARK_CIRCLE, $::circle_img[$dark_bg]],
143 [MARK_TRIANGLE, $::triangle_img[$dark_bg]],
144 [MARK_SQUARE, $::square_img[$dark_bg]]) {
145 my ($mask, $img) = @$_;
146 if ($mark & $mask) {
147 $img->composite (
148 $base, 0, 0, $size, $size, 0, 0,
149 $size / $img->get_width, $size / $img->get_height,
150 $::config->{speed} ? 'tiles' : 'bilinear', 192
151 );
152 }
153 }
154
155 push @$$c, $base;
156 }
157 }
158
159 $$c->[$rand % @$$c];
160 }
161
162 sub pixbuf_text {
163 my ($self, $pixbuf, $colour, $x, $y, $height, $text) = @_;
164
165 #my $layout = $self->create_pango_layout ($text);
166 #my ($w, $h) = $layout->get_pixel_size;
167 #print "$w $h\n";#d#
168
169 my @c = grep $_,
170 map $::font[$colour][$::fontmap{$_}],
171 split //, $text;
172
173 if (@c) {
174 my $spacing = $height * 0.1;
175 my $s = $height / List::Util::max map $_->get_height, @c;
176 my $W = List::Util::sum map $_->get_width, @c;
177
178 $x -= ($W * $s + $spacing * (@c - 1)) * 0.5;
179 $y -= $height * 0.5;
180
181 for (@c) {
182 my $w = $_->get_width * $s;
183 # +2 == don't fight the rounding
184 $_->composite ($pixbuf,
185 $x, $y, $w+2, $height+2, $x, $y, $s, $s,
186 $::config->{speed} ? 'tiles' : 'bilinear', 255);
187
188 $x += $w + $spacing;
189 }
190 }
191 }
192
193 sub pixbuf_rect {
194 my ($pb, $colour, $x1, $y1, $x2, $y2, $alpha) = @_;
195 # we fake lines by... a horrible method :/
196 my $colour_pb = new_pixbuf 1, 1, 0, $colour;
197 $colour_pb->composite ($pb, $x1, $y1, $x2 - $x1 + 1, $y2 - $y1 + 1, $x1, $y1, $x2 + 1, $y2 + 1,
198 'nearest', $alpha);
199 }
200
201 sub set_board {
202 my ($self, $board) = @_;
203
204 $self->repaint_board ($board, 1);
205 }
206
207 # draw an empty board
208 sub draw_board {
209 my ($self, $s) = @_;
210 my $canvas = $self->{canvas};
211
212 my $size = $self->{size};
213
214 # we leave enough space for the shadows.. I like smaller stones, and we
215 # do no need to do the nifty recursive screen updates that goban2 does
216 my $border = int ($s / ($size + 3) * 0.5);
217 my $s2 = $s - $border * 2;
218 my $edge = $self->{edge} = int ($s2 / ($size + 1) * 0.975);
219 my $ofs = int ($edge / 2);
220
221 my @k = map int ($s2 * $_ / ($size+1) + $border + 0.5), 0 .. $size;
222
223 $self->{k} = \@k;
224
225 my $pixbuf;
226
227 my ($bw, $bh) = ($::board_img->get_width, $::board_img->get_height);
228
229 if ($s < $bw && $s < $bh) {
230 $pixbuf = new_pixbuf $s, $s, 0;
231 $::board_img->copy_area (0, 0, $s, $s, $pixbuf, 0, 0);
232 } else {
233 $pixbuf = scale_pixbuf $::board_img, $s, $s, $::config->{speed} ? 'nearest' : 'bilinear', 0;
234 }
235
236 my $linew = int ($s / 40 / $size);
237
238 # ornamental border... we have time to waste :/
239 pixbuf_rect $pixbuf, 0xffcc7700, 0, 0, $s-1, $linew, 255;
240 pixbuf_rect $pixbuf, 0xffcc7700, 0, 0, $linew, $s-1, 255;
241 pixbuf_rect $pixbuf, 0xffcc7700, $s-$linew-1, 0, $s-1, $s-1, 255;
242 pixbuf_rect $pixbuf, 0xffcc7700, 0, $s-$linew-1, $s-1, $s-1, 255;
243
244 for my $i (1 .. $size) {
245 pixbuf_rect $pixbuf, 0x44111100, $k[$i] - $linew, $k[1] - $linew, $k[$i] + $linew, $k[$size] + $linew, 192;
246 pixbuf_rect $pixbuf, 0x44111100, $k[1] - $linew, $k[$i] - $linew, $k[$size] + $linew, $k[$i] + $linew, 192;
247
248 # 38 max, but we allow a bit more
249 my $label = (qw(- A B C D E F G H J K L M N O P Q R S T U V W X Y Z
250 AA BB CC DD EE FF GG HH JJ KK LL MM NN OO PP QQ RR SS TT UU VV WW XX YY ZZ))[$i];
251
252 pixbuf_text $self, $pixbuf, 0, $k[$i], $border, $ofs, $label;
253 pixbuf_text $self, $pixbuf, 0, $k[$i], $s2 + $border, $ofs, $label;
254 pixbuf_text $self, $pixbuf, 0, $border, $k[$i], $ofs, $size - $i + 1;
255 pixbuf_text $self, $pixbuf, 0, $s2 + $border, $k[$i], $ofs, $size - $i + 1;
256
257 $a++;
258 $a++ if $a eq "I"; # not correct, instead of AA AB, we should get HH JJ KK...
259 }
260
261 # hoshi points
262 my $hoshi = sub {
263 my ($x, $y) = @_;
264 my $hs = int ($edge / 4) | 1;
265 $x = $k[$x] - $hs / 2; $y = $k[$y] - $hs / 2;
266
267 # we use the shadow mask... not perfect, but I want to finish this
268 $::shadow_img->composite ($pixbuf,
269 $x, $y, $hs + 1, $hs + 1, $x, $y,
270 $hs / $::shadow_img->get_width, $hs / $::shadow_img->get_height,
271 $::config->{speed} ? 'tiles' : 'bilinear', 255);
272 };
273
274 my $h1 = $size < 10 ? 3 : 4; # corner / edge offset
275 $hoshi->($h1, $h1);
276 $hoshi->($size - $h1 + 1, $h1);
277 $hoshi->($h1, $size - $h1 + 1);
278 $hoshi->($size - $h1 + 1, $size - $h1 + 1);
279
280 if ($size % 2) { # on odd boards, also the remaining 5
281 my $h2 = ($size + 1) / 2;
282 if ($size > 10) {
283 $hoshi->($h1, $h2);
284 $hoshi->($size - $h1 + 1, $h2);
285 $hoshi->($h2, $size - $h1 + 1);
286 $hoshi->($h2, $h1);
287 }
288 # the tengen
289 $hoshi->($h2, $h2);
290 }
291
292 $pixbuf;
293 }
294
295 sub repaint_board {
296 my ($self, $board, $dopaint) = @_;
297
298 my @areas;
299
300 my $old = $self->{board};
301 my $size = $self->{size};
302 my $edge = $self->{edge};
303 my $ofs = int ($edge * 0.5);
304 my $k = $self->{k};
305
306 # repaint
307 for my $x (1 .. $size) {
308 for my $y (1 .. $size) {
309 my $mark = $board->{board}[$x-1][$y-1];
310 my $old = $old->{board}[$x-1][$y-1];
311
312 if ($old != $mark) {
313 my $rand = ($x ^ $y ^ 0x5555);
314
315 my $shadow = $edge * 0.05;
316 my @area = ($k->[$x] - $ofs, $k->[$y] - $ofs,
317 $edge + $shadow, $edge + $shadow);
318
319 my $pb = new_pixbuf @area[2,3];
320 $self->{backgroundpb}->copy_area (@area, $pb, 0, 0);
321
322 if ($mark) {
323 my $stack = $self->create_stack($mark, $edge, $rand);
324
325 $stack->composite ($pb, 0, 0, @area[2,3], 0, 0, 1, 1,
326 'nearest', 255);
327
328 # labels are handled here because they are quite rare
329 if ($mark & MARK_LABEL) {
330 my $white = $mark & (MARK_W | MARK_GRAY_W) ? 0 : 1;
331
332 if ($white) {
333 pixbuf_text $pb, 0,
334 $ofs * 0.1, $ofs * 0.1, $ofs * 0.7,
335 $self->{board}{label}[$x-1][$y-1];
336 }
337 pixbuf_text $pb, $white,
338 0, 0, $ofs * 0.7,
339 $self->{board}{label}[$x-1][$y-1];
340 }
341 }
342
343 # speed none, normal, max
344 $self->{backgroundpm}->draw_pixbuf ($self->style->black_gc, $pb,
345 0, 0, @area, 'max', 0, 0);
346 # a single full clear_area is way faster than many single calls here
347 push @areas, \@area if $dopaint;
348 }
349 }
350 }
351
352 if (@areas) {
353 # the "cut-off" point is arbitrary
354 if (@areas > 16) {
355 # update a single rectangle only
356 my $rect = new Gtk2::Gdk::Rectangle @{pop @areas};
357 $rect = $rect->union (new Gtk2::Gdk::Rectangle @$_) for @areas;
358 $self->window->clear_area ($rect->values);
359 } else {
360 # update all the affected rectangles
361 $self->window->clear_area (@$_) for @areas;
362 }
363 }
364
365 $self->{board} = $board;
366 #d# save
367 #Storable::nstore { board => $self->{board}, size => $self->{size}, path => $self->{path}}, "testboard.storable";
368 }
369
370 sub redraw {
371 my ($self, $area) = @_;
372
373 if ($area && $self->{pixbuf}) {
374 my ($x, $y, $w, $h) = $area->values;
375
376 $self->{canvas}->window->draw_pixbuf ($self->{canvas}->style->white_gc, $self->{pixbuf},
377 $x, $y, $x, $y, $w, $h,
378 "normal", 0, 0);
379 $self->{canvas}->window->draw_rectangle ($self->{canvas}->style->black_gc, 0,
380 $x - 1, $y - 1, $w + 2, $h + 2) if $::DEBUG_EXPOSE;
381 }
382 }
383
384 sub FINALIZE {
385 warn "FINALIZE(@_)\n";#d#
386 my ($self) = @_;
387 $self->{userpanel}[$_] && (delete $self->{userpanel}[$_])->destroy
388 for BLACK, WHITE;
389 $self->SUPER::destroy;
390 delete $appwin::gamelist->{game}{$self->{channel}};
391 }
392
393 1;
394