1 // Copyright (C) 2017, 2020 Ben Asselstine
2 //
3 // This program is free software; you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation; either version 3 of the License, or
6 // (at your option) any later version.
7 //
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU Library General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with this program; if not, write to the Free Software
15 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
16 // 02110-1301, USA.
17
18 #include <config.h>
19
20 #include <gtkmm.h>
21 #include <sigc++/functors/mem_fun.h>
22
23 #include "battle-calculator-dialog.h"
24
25 #include "ucompose.hpp"
26 #include "defs.h"
27 #include "stack.h"
28 #include "army.h"
29 #include "armyproto.h"
30 #include "hero.h"
31 #include "heroproto.h"
32 #include "ImageCache.h"
33 #include "playerlist.h"
34 #include "stacklist.h"
35 #include "stacktile.h"
36 #include "hero-editor-dialog.h"
37 #include "GameMap.h"
38 #include "tileset.h"
39 #include "fight.h"
40 #include "font-size.h"
41
42 #include "select-army-dialog.h"
43
44 #define method(x) sigc::mem_fun(*this, &BattleCalculatorDialog::x)
45
BattleCalculatorDialog(Gtk::Window & parent,std::list<Army * > & attackers,std::list<Army * > & defenders)46 BattleCalculatorDialog::BattleCalculatorDialog(Gtk::Window &parent, std::list<Army*> &attackers, std::list<Army *> &defenders)
47 : LwEditorDialog(parent, "battle-calculator-dialog.ui"),
48 attacker_strength_column(_("Strength"), attacker_strength_renderer),
49 defender_strength_column(_("Strength"), defender_strength_renderer),
50 d_attackers(attackers), d_defenders(defenders)
51 {
52 attacker_player_combobox = NULL;
53 attacker_player_combobox = manage(new Gtk::ComboBoxText);
54 for (auto p : *Playerlist::getInstance())
55 attacker_player_combobox->append(p->getName());
56 attacker_player_combobox->set_active(0);
57 attacker_player_combobox->signal_changed().connect (method(on_attacker_player_changed));
58 Gtk::Box *box1;
59 xml->get_widget("attacker_hbox", box1);
60 box1->pack_start(*attacker_player_combobox, Gtk::PACK_SHRINK);
61
62 defender_player_combobox = NULL;
63 defender_player_combobox = manage(new Gtk::ComboBoxText);
64 for (auto p : *Playerlist::getInstance())
65 defender_player_combobox->append(p->getName());
66 defender_player_combobox->set_active(0);
67 defender_player_combobox->signal_changed().connect (method(on_defender_player_changed));
68 Gtk::Box *box2;
69 xml->get_widget("defender_hbox", box2);
70 box2->pack_start(*defender_player_combobox, Gtk::PACK_SHRINK);
71
72 attackers_list = Gtk::ListStore::create(combatant_columns);
73 xml->get_widget("attackers_treeview", attackers_treeview);
74 attackers_treeview->set_model(attackers_list);
75
76 attackers_treeview->append_column("", combatant_columns.image);
77
78 attacker_strength_renderer.property_editable() = true;
79 attacker_strength_renderer.signal_edited().connect(method(on_attacker_strength_edited));
80 attacker_strength_column.set_cell_data_func(attacker_strength_renderer, method(cell_data_attacker_strength));
81 attackers_treeview->append_column(attacker_strength_column);
82 attackers_treeview->append_column(_("Augmented Str"), combatant_columns.augmented_strength);
83 attackers_treeview->append_column(_("HP"), combatant_columns.hp);
84 for (guint32 i = 0; i < attackers_treeview->get_n_columns(); i++)
85 attackers_treeview->get_column(i)->set_expand();
86
87 defenders_list = Gtk::ListStore::create(combatant_columns);
88 xml->get_widget("defenders_treeview", defenders_treeview);
89 defenders_treeview->set_model(defenders_list);
90
91 defenders_treeview->append_column("", combatant_columns.image);
92
93 defender_strength_renderer.property_editable() = true;
94 defender_strength_renderer.signal_edited().connect(method(on_defender_strength_edited));
95 defender_strength_column.set_cell_data_func(defender_strength_renderer, method(cell_data_defender_strength));
96 defenders_treeview->append_column(defender_strength_column);
97 defenders_treeview->append_column(_("Augmented Str"), combatant_columns.augmented_strength);
98 defenders_treeview->append_column(_("HP"), combatant_columns.hp);
99 for (guint32 i = 0; i < attackers_treeview->get_n_columns(); i++)
100 defenders_treeview->get_column(i)->set_expand();
101 xml->get_widget("fortified_switch", fortified_switch);
102
103 xml->get_widget("attacker_add_button", attacker_add_button);
104 xml->get_widget("attacker_remove_button", attacker_remove_button);
105 xml->get_widget("attacker_copy_button", attacker_copy_button);
106 xml->get_widget("attacker_edit_hero_button", attacker_edit_hero_button);
107
108 attacker_add_button->signal_clicked().connect(method(on_attacker_add_clicked));
109 attacker_remove_button->signal_clicked().connect(method(on_attacker_remove_clicked));
110 attacker_copy_button->signal_clicked().connect(method(on_attacker_copy_clicked));
111 attacker_edit_hero_button->signal_clicked().connect(method(on_attacker_edit_hero_clicked));
112
113 attackers_treeview->get_selection()->signal_changed().connect(method(on_attacker_selection_changed));
114
115 xml->get_widget("defender_add_button", defender_add_button);
116 xml->get_widget("defender_remove_button", defender_remove_button);
117 xml->get_widget("defender_copy_button", defender_copy_button);
118 xml->get_widget("defender_edit_hero_button", defender_edit_hero_button);
119
120 defender_add_button->signal_clicked().connect(method(on_defender_add_clicked));
121 defender_remove_button->signal_clicked().connect(method(on_defender_remove_clicked));
122 defender_copy_button->signal_clicked().connect(method(on_defender_copy_clicked));
123 defender_edit_hero_button->signal_clicked().connect(method(on_defender_edit_hero_clicked));
124
125 defenders_treeview->get_selection()->signal_changed().connect(method(on_defender_selection_changed));
126 xml->get_widget("fight_button", fight_button);
127 fight_button->signal_clicked().connect(method(on_fight_clicked));
128 xml->get_widget("fight100_button", fight100_button);
129 fight100_button->signal_clicked().connect(method(on_fight100_clicked));
130
131 xml->get_widget("city_switch", city_switch);
132 city_switch->property_active().signal_changed().connect(method(on_city_toggled));
133 xml->get_widget("terrain_box", terrain_box);
134 terrain_combobox = manage(new Gtk::ComboBoxText);
135 Tileset *tileset = GameMap::getTileset();
136 for (auto t : *tileset)
137 terrain_combobox->append(t->getName());
138 terrain_combobox->set_active(0);
139 terrain_box->pack_start(*terrain_combobox, Gtk::PACK_SHRINK);
140 xml->get_widget("die_sides_combobox", die_sides_combobox);
141 for (auto a : d_attackers)
142 add_attacker_army (a, false);
143 for (auto a : d_defenders)
144 add_defender_army (a, false);
145 set_button_sensitivity();
146 }
147
run()148 int BattleCalculatorDialog::run()
149 {
150 dialog->show_all();
151 return dialog->run();
152 }
153
get_attacker_player()154 Player *BattleCalculatorDialog::get_attacker_player()
155 {
156 int c = 0, row = attacker_player_combobox->get_active_row_number();
157 Player *player = Playerlist::getInstance()->getNeutral();
158 for (Playerlist::iterator i = Playerlist::getInstance()->begin(),
159 end = Playerlist::getInstance()->end(); i != end; ++i, ++c)
160 if (c == row)
161 {
162 player = *i;
163 break;
164 }
165 return player;
166 }
167
on_attacker_copy_clicked()168 void BattleCalculatorDialog::on_attacker_copy_clicked()
169 {
170 Gtk::TreeIter i = attackers_treeview->get_selection()->get_selected();
171 if (i)
172 {
173 Player *player = get_attacker_player();
174 Army *army = (*i)[combatant_columns.army];
175 Army *new_army = new Army(*army, player);
176 new_army->assignNewId();
177 add_attacker_army(new_army, true);
178 }
179
180 set_button_sensitivity();
181 }
182
on_attacker_add_clicked()183 void BattleCalculatorDialog::on_attacker_add_clicked()
184 {
185 SelectArmyDialog d(*dialog, SelectArmyDialog::SELECT_NORMAL_WITH_HERO,
186 get_attacker_player(), -1);
187 d.run();
188
189 Player *player = get_attacker_player();
190 const ArmyProto *army = d.get_selected_army();
191 if (army)
192 {
193 if (army->isHero() == true)
194 {
195 HeroProto *hp = new HeroProto(*army);
196 hp->setOwnerId(player->getId());
197 add_attacker_army(new Hero (*hp), true);
198 delete hp;
199 }
200 else
201 add_attacker_army(new Army(*army, player), true);
202 }
203 }
204
205
on_attacker_edit_hero_clicked()206 void BattleCalculatorDialog::on_attacker_edit_hero_clicked()
207 {
208 Gtk::TreeIter i = attackers_treeview->get_selection()->get_selected();
209 if (i)
210 {
211 Army *army = (*i)[combatant_columns.army];
212 Hero *hero = dynamic_cast<Hero*>(army);
213 HeroEditorDialog d(*dialog, hero);
214 d.run();
215 }
216 }
217
on_attacker_remove_clicked()218 void BattleCalculatorDialog::on_attacker_remove_clicked()
219 {
220 Gtk::TreeIter i = attackers_treeview->get_selection()->get_selected();
221 if (i)
222 {
223 Army *army = (*i)[combatant_columns.army];
224 d_attackers.erase(std::remove (d_attackers.begin(), d_attackers.end(), army), d_attackers.end());
225 delete army;
226 attackers_list->erase(i);
227
228 }
229
230 set_button_sensitivity();
231 }
232
add_attacker_army(Army * a,bool add)233 void BattleCalculatorDialog::add_attacker_army(Army *a, bool add)
234 {
235 if (add)
236 d_attackers.push_back(a);
237 ImageCache *gc = ImageCache::getInstance();
238 Gtk::TreeIter i = attackers_list->append();
239 (*i)[combatant_columns.army] = a;
240 guint32 fs = FontSize::getInstance ()->get_height ();
241 (*i)[combatant_columns.image] = gc->getDialogArmyPic (a, fs)->to_pixbuf ();
242 (*i)[combatant_columns.strength] = a->getStat(Army::STRENGTH, false);
243 (*i)[combatant_columns.hp] = a->getStat(Army::HP, false);
244
245 attackers_treeview->get_selection()->select(i);
246
247 set_button_sensitivity();
248 }
249
on_attacker_selection_changed()250 void BattleCalculatorDialog::on_attacker_selection_changed()
251 {
252 set_button_sensitivity();
253 }
254
on_attacker_player_changed()255 void BattleCalculatorDialog::on_attacker_player_changed()
256 {
257 ImageCache *gc = ImageCache::getInstance();
258 Player *player = get_attacker_player();
259 set_button_sensitivity();
260
261 guint32 fs = FontSize::getInstance ()->get_height ();
262 for (Gtk::TreeIter j = attackers_list->children().begin(),
263 jend = attackers_list->children().end(); j != jend; ++j)
264 {
265 Army *a = (*j)[combatant_columns.army];
266 a->setOwner (player);
267 (*j)[combatant_columns.image] =
268 gc->getDialogArmyPic(a, fs)->to_pixbuf ();
269 }
270 }
271
cell_data_attacker_strength(Gtk::CellRenderer * renderer,const Gtk::TreeIter & i)272 void BattleCalculatorDialog::cell_data_attacker_strength(Gtk::CellRenderer *renderer, const Gtk::TreeIter& i)
273 {
274 dynamic_cast<Gtk::CellRendererSpin*>(renderer)->property_adjustment()
275 = Gtk::Adjustment::create((*i)[combatant_columns.strength],
276 MIN_STRENGTH_FOR_ARMY_UNITS,
277 MAX_STRENGTH_FOR_ARMY_UNITS, 1);
278 dynamic_cast<Gtk::CellRendererSpin*>(renderer)->property_text() =
279 String::ucompose("%1", (*i)[combatant_columns.strength]);
280 }
281
on_attacker_strength_edited(const Glib::ustring & path,const Glib::ustring & new_text)282 void BattleCalculatorDialog::on_attacker_strength_edited(const Glib::ustring &path, const Glib::ustring &new_text)
283 {
284 int str = atoi(new_text.c_str());
285 if (str < (int)MIN_STRENGTH_FOR_ARMY_UNITS || str >
286 (int)MAX_STRENGTH_FOR_ARMY_UNITS)
287 return;
288 (*attackers_list->get_iter(Gtk::TreePath(path)))[combatant_columns.strength] = str;
289 }
290
set_button_sensitivity()291 void BattleCalculatorDialog::set_button_sensitivity()
292 {
293 int att = 0, def = 0;
294 {
295 int max_number_of_attackers = 8;
296 Gtk::TreeIter i = attackers_treeview->get_selection()->get_selected();
297 att = attackers_list->children().size();
298 attacker_add_button->set_sensitive(att < max_number_of_attackers);
299 attacker_copy_button->set_sensitive(i && att < max_number_of_attackers);
300 attacker_remove_button->set_sensitive(i);
301 if (i)
302 {
303 Army *army = (*i)[combatant_columns.army];
304 if (army->isHero())
305 {
306 attacker_edit_hero_button->set_sensitive(true);
307 attacker_copy_button->set_sensitive(false);
308 }
309 else
310 attacker_edit_hero_button->set_sensitive(false);
311 }
312 else
313 attacker_edit_hero_button->set_sensitive(false);
314 }
315 {
316 int max_number_of_defenders = 32;
317 Gtk::TreeIter i = defenders_treeview->get_selection()->get_selected();
318 def = defenders_list->children().size();
319 defender_add_button->set_sensitive(def < max_number_of_defenders);
320 defender_copy_button->set_sensitive(i && def < max_number_of_defenders);
321 defender_remove_button->set_sensitive(i);
322 if (i)
323 {
324 Army *army = (*i)[combatant_columns.army];
325 if (army->isHero())
326 {
327 defender_edit_hero_button->set_sensitive(true);
328 defender_copy_button->set_sensitive(false);
329 }
330 else
331 defender_edit_hero_button->set_sensitive(false);
332 }
333 else
334 defender_edit_hero_button->set_sensitive(false);
335 }
336 fight_button->set_sensitive (att && def);
337 fight100_button->set_sensitive (att && def);
338 }
339
get_defender_player()340 Player *BattleCalculatorDialog::get_defender_player()
341 {
342 int c = 0, row = defender_player_combobox->get_active_row_number();
343 Player *player = Playerlist::getInstance()->getNeutral();
344 for (Playerlist::iterator i = Playerlist::getInstance()->begin(),
345 end = Playerlist::getInstance()->end(); i != end; ++i, ++c)
346 if (c == row)
347 {
348 player = *i;
349 break;
350 }
351 return player;
352 }
353
on_defender_copy_clicked()354 void BattleCalculatorDialog::on_defender_copy_clicked()
355 {
356 Gtk::TreeIter i = defenders_treeview->get_selection()->get_selected();
357 if (i)
358 {
359 Player *player = get_defender_player();
360 Army *army = (*i)[combatant_columns.army];
361 Army *new_army = new Army(*army, player);
362 new_army->assignNewId();
363 add_defender_army(new_army, true);
364 }
365
366 set_button_sensitivity();
367 }
368
on_defender_add_clicked()369 void BattleCalculatorDialog::on_defender_add_clicked()
370 {
371 SelectArmyDialog d(*dialog, SelectArmyDialog::SELECT_NORMAL_WITH_HERO,
372 get_defender_player(), -1);
373 d.run();
374
375 Player *player = get_defender_player();
376 const ArmyProto *army = d.get_selected_army();
377 if (army)
378 {
379 if (army->isHero() == true)
380 {
381 HeroProto *hp = new HeroProto(*army);
382 hp->setOwnerId(player->getId());
383 add_defender_army(new Hero(*hp), true);
384 delete hp;
385 }
386 else
387 add_defender_army(new Army(*army, player), true);
388 }
389 }
390
on_defender_edit_hero_clicked()391 void BattleCalculatorDialog::on_defender_edit_hero_clicked()
392 {
393 Gtk::TreeIter i = defenders_treeview->get_selection()->get_selected();
394 if (i)
395 {
396 Army *army = (*i)[combatant_columns.army];
397 Hero *hero = dynamic_cast<Hero*>(army);
398 HeroEditorDialog d(*dialog, hero);
399 d.run();
400 }
401 }
402
on_defender_remove_clicked()403 void BattleCalculatorDialog::on_defender_remove_clicked()
404 {
405 Gtk::TreeIter i = defenders_treeview->get_selection()->get_selected();
406 if (i)
407 {
408 Army *army = (*i)[combatant_columns.army];
409 d_defenders.erase(std::remove (d_defenders.begin(), d_defenders.end(), army), d_defenders.end());
410 delete army;
411 defenders_list->erase(i);
412 }
413
414 set_button_sensitivity();
415 }
416
add_defender_army(Army * a,bool add)417 void BattleCalculatorDialog::add_defender_army(Army *a, bool add)
418 {
419 if (add)
420 d_defenders.push_back(a);
421 ImageCache *gc = ImageCache::getInstance();
422 Gtk::TreeIter i = defenders_list->append();
423 (*i)[combatant_columns.army] = a;
424 guint32 fs = FontSize::getInstance ()->get_height ();
425 (*i)[combatant_columns.image] = gc->getDialogArmyPic (a, fs)->to_pixbuf ();
426 (*i)[combatant_columns.strength] = a->getStat(Army::STRENGTH, false);
427 //(*i)[combatant_columns.augmented_strength] =
428 //a->getStat(Army::STRENGTH, false);
429 (*i)[combatant_columns.hp] = a->getStat(Army::HP, false);
430
431 defenders_treeview->get_selection()->select(i);
432
433 set_button_sensitivity();
434 }
435
on_defender_selection_changed()436 void BattleCalculatorDialog::on_defender_selection_changed()
437 {
438 set_button_sensitivity();
439 }
440
on_defender_player_changed()441 void BattleCalculatorDialog::on_defender_player_changed()
442 {
443 ImageCache *gc = ImageCache::getInstance();
444 Player *player = get_defender_player();
445 set_button_sensitivity();
446
447 guint32 fs = FontSize::getInstance ()->get_height ();
448 for (Gtk::TreeIter j = defenders_list->children().begin(),
449 jend = defenders_list->children().end(); j != jend; ++j)
450 {
451 Army *a = (*j)[combatant_columns.army];
452 a->setOwner (player);
453 (*j)[combatant_columns.image] =
454 gc->getDialogArmyPic(a, fs)->to_pixbuf ();
455 }
456 }
457
cell_data_defender_strength(Gtk::CellRenderer * renderer,const Gtk::TreeIter & i)458 void BattleCalculatorDialog::cell_data_defender_strength(Gtk::CellRenderer *renderer, const Gtk::TreeIter& i)
459 {
460 dynamic_cast<Gtk::CellRendererSpin*>(renderer)->property_adjustment()
461 = Gtk::Adjustment::create((*i)[combatant_columns.strength],
462 MIN_STRENGTH_FOR_ARMY_UNITS,
463 MAX_STRENGTH_FOR_ARMY_UNITS, 1);
464 dynamic_cast<Gtk::CellRendererSpin*>(renderer)->property_text() =
465 String::ucompose("%1", (*i)[combatant_columns.strength]);
466 }
467
on_defender_strength_edited(const Glib::ustring & path,const Glib::ustring & new_text)468 void BattleCalculatorDialog::on_defender_strength_edited(const Glib::ustring &path, const Glib::ustring &new_text)
469 {
470 int str = atoi(new_text.c_str());
471 if (str < (int)MIN_STRENGTH_FOR_ARMY_UNITS || str >
472 (int)MAX_STRENGTH_FOR_ARMY_UNITS)
473 return;
474 (*defenders_list->get_iter(Gtk::TreePath(path)))[combatant_columns.strength] = str;
475 }
476
on_city_toggled()477 void BattleCalculatorDialog::on_city_toggled()
478 {
479 if (city_switch->property_active () == true)
480 terrain_combobox->set_active (0);
481 terrain_combobox->set_sensitive(!city_switch->property_active());
482 }
483
run_battle()484 Fight::Result BattleCalculatorDialog::run_battle ()
485 {
486 //load the attackers into a single stack
487 std::list<Stack*> attackers;
488 Stack *stack = new Stack(get_attacker_player(), Vector<int>(-1,-1));
489 attackers.push_back (stack);
490 for (auto a: attackers_list->children())
491 {
492 Army *army = (*a)[combatant_columns.army];
493 stack->add (army);
494 }
495
496 //load the defenders into a bunch of stacks
497 std::list<Stack*> defenders;
498 stack = new Stack(get_defender_player(), Vector<int>(-1,-1));
499 defenders.push_back (stack);
500 for (auto d: defenders_list->children())
501 {
502 if (stack->size() == 8)
503 {
504 stack = new Stack(get_defender_player(), Vector<int>(-1,-1));
505 defenders.push_back (stack);
506 }
507 Army *army = (*d)[combatant_columns.army];
508 stack->add (army);
509 }
510 Tileset *tileset = GameMap::getTileset();
511 int row = terrain_combobox->get_active_row_number();
512 bool water = (*tileset)[row]->getType() == Tile::WATER;
513 //put them all in the water if we're doing that.
514 for (auto s : attackers)
515 for (auto a : *s)
516 a->setInShip(water);
517 for (auto s : defenders)
518 for (auto a : *s)
519 a->setInShip(water);
520
521 //fortify them if we're doing that
522 for (auto s : defenders)
523 for (auto a : *s)
524 a->setFortified(fortified_switch->get_active());
525 Fight f(attackers, defenders, city_switch->get_active(), Tile::Type(row));
526 for (auto a: attackers_list->children())
527 {
528 Army *army = (*a)[combatant_columns.army];
529 (*a)[combatant_columns.augmented_strength] =
530 f.getModifiedStrengthBonus(army);
531 }
532 for (auto d: defenders_list->children())
533 {
534 Army *army = (*d)[combatant_columns.army];
535 (*d)[combatant_columns.augmented_strength] =
536 f.getModifiedStrengthBonus(army);
537 }
538 f.battle(die_sides_combobox->get_active_row_number() == 1);
539 for (auto a: attackers_list->children())
540 {
541 Army *army = (*a)[combatant_columns.army];
542 (*a)[combatant_columns.hp] = army->getHP();
543 }
544 for (auto d: defenders_list->children())
545 {
546 Army *army = (*d)[combatant_columns.army];
547 (*d)[combatant_columns.hp] = army->getHP();
548 }
549 //reset the HP
550 std::map<guint32,guint32> initial_hitpoints = f.getInitialHPs();
551 for (auto a: attackers_list->children())
552 {
553 Army *army = (*a)[combatant_columns.army];
554 army->setHP(initial_hitpoints[army->getId()]);
555 }
556 for (auto d: defenders_list->children())
557 {
558 Army *army = (*d)[combatant_columns.army];
559 army->setHP(initial_hitpoints[army->getId()]);
560 }
561 //delete the stacks we made, but keep the armies
562 for (auto s: attackers)
563 {
564 s->clear();
565 delete s;
566 }
567 for (auto d: defenders)
568 {
569 d->clear();
570 delete d;
571 }
572 return f.getResult();
573 }
574
on_fight_clicked()575 void BattleCalculatorDialog::on_fight_clicked()
576 {
577 run_battle ();
578 }
579
on_fight100_clicked()580 void BattleCalculatorDialog::on_fight100_clicked()
581 {
582 int attacker_wins = 0;
583 int defender_wins = 0;
584 for (int i = 0; i < 100; i++)
585 {
586 switch (run_battle())
587 {
588 case Fight::ATTACKER_WON:
589 attacker_wins++;
590 break;
591 case Fight::DEFENDER_WON:
592 defender_wins++;
593 break;
594 case Fight::DRAW:
595 break;
596 }
597 }
598
599 //show results
600 {
601 Gtk::Dialog *d = new Gtk::Dialog();
602 d->property_transient_for() = dialog;
603 d->add_button(Gtk::Stock::CLOSE, Gtk::RESPONSE_ACCEPT);
604 Gtk::Box *box = d->get_content_area ();
605 d->set_title (_("Battle Outcome"));
606 Glib::ustring s =
607 String::ucompose(ngettext("The attacker won %1 battle and lost %2.",
608 "The attacker won %1 battles and lost %2.",
609 attacker_wins),
610 attacker_wins, defender_wins);
611 Gtk::Label l;
612 l.set_text (s);
613 l.set_margin_left (10);
614 l.set_margin_right (10);
615 l.set_margin_top (10);
616 l.set_margin_bottom (10);
617 box->add(l);
618 box->show_all();
619 d->run();
620 delete d;
621 }
622 }
623