001 package com.croftsoft.apps.insight; 002 003 import java.awt.*; 004 import java.awt.event.*; 005 import java.io.*; 006 import javax.swing.*; 007 008 import com.croftsoft.core.awt.font.FontLib; 009 import com.croftsoft.core.gui.FrameLib; 010 import com.croftsoft.core.gui.plot.PlotLib; 011 import com.croftsoft.core.lang.lifecycle.Lifecycle; 012 import com.croftsoft.core.math.RandomLib; 013 import com.croftsoft.core.animation.*; 014 import com.croftsoft.core.animation.component.*; 015 016 /********************************************************************* 017 * Goblins hunt kobolds in the dark using a neural network. 018 * 019 * @version 020 * 2002-03-23 021 * @since 022 * 1996-09-06 023 * @author 024 * <a href="https://www.croftsoft.com/">David Wallace Croft</a> 025 *********************************************************************/ 026 027 public class Insight 028 extends JApplet 029 implements ActionListener, ComponentListener, ItemListener, 030 Lifecycle, ComponentAnimator 031 ////////////////////////////////////////////////////////////////////// 032 ////////////////////////////////////////////////////////////////////// 033 { 034 035 private static final String VERSION = "2002-03-23"; 036 037 private static final String TITLE = "CroftSoft Insight"; 038 039 private static final String INFO 040 = "\n" + TITLE + "\n" 041 + "Copyright 2002 CroftSoft Inc\n" 042 + "https://www.croftsoft.com/\n" 043 + "Version " + VERSION + "\n"; 044 045 private static final double FRAME_RATE = 2.0; 046 047 private static final Dimension FRAME_SIZE = null; 048 049 private static final String FRAME_ICON_FILENAME = "/images/david.png"; 050 051 private static final String SHUTDOWN_CONFIRMATION_PROMPT 052 = "Close " + TITLE + "?"; 053 054 private static final String FONT_NAME = "TimesRoman"; 055 056 private static final int FONT_STYLE = Font.BOLD; 057 058 // 059 060 private static final int ETHER = 0; 061 062 private static final int WALL = 1; 063 064 private static final int GOBLIN = 2; 065 066 private static final int KOBOLD = 3; 067 068 private static final int ELEMENT_COUNT = 4; 069 070 private static final Color [ ] ELEMENT_COLOR = { 071 Color.BLACK, 072 Color.GRAY, 073 Color.MAGENTA, 074 Color.GREEN }; 075 076 private static final String [ ] ELEMENT_NAME = { 077 "empty", 078 "wall", 079 "goblin", 080 "kobold" }; 081 082 // 083 084 private static final Point [ ] DIRECTIONS = { 085 new Point ( -1, 1 ), 086 new Point ( 0, 1 ), 087 new Point ( 1, 1 ), 088 new Point ( -1, 0 ), 089 new Point ( 1, 0 ), 090 new Point ( -1, -1 ), 091 new Point ( 0, -1 ), 092 new Point ( 1, -1 ) }; 093 094 private static final int GOBLIN_BRAIN_OUTPUTS = DIRECTIONS.length; 095 096 private static final int [ ] neurons_per_layer 097 = { 64, GOBLIN_BRAIN_OUTPUTS }; 098 099 // 100 101 private static final int BATTLEFIELD_WIDTH = 100; 102 103 private static final int BATTLEFIELD_HEIGHT = 100; 104 105 // 106 107 private static final int BORDER_WALL_COUNT 108 = 2 * BATTLEFIELD_WIDTH 109 + 2 * BATTLEFIELD_HEIGHT - 4; // overlap at 4 corners 110 111 private static final int WALL_COUNT = BORDER_WALL_COUNT + 100; 112 113 private static final int GOBLIN_COUNT = 100; 114 115 private static final int KOBOLD_COUNT = 100; 116 117 // 118 119 private Thing [ ] goblins; 120 121 private Thing [ ] kobolds; 122 123 private Thing [ ] walls; 124 125 private Thing [ ] [ ] space_contents; 126 127 // 128 129 private int goblins_alive_count = GOBLIN_COUNT; 130 131 private int kobolds_alive_count = KOBOLD_COUNT; 132 133 private long goblins_killed = 0; 134 135 private long kobolds_killed = 0; 136 137 private long goblins_killed_by_kobolds = 0; 138 139 private long kobolds_killed_by_goblins = 0; 140 141 // 142 143 private boolean goblin_learning_on = false; 144 145 private int [ ] direction_button_element; 146 147 private Matrix goblin_brain_inputs; 148 149 private Backprop_Net goblin_brain; 150 151 private int goblin_best_direction; 152 153 // 154 155 private AnimatedComponent animatedComponent; 156 157 private JCheckBox learningOnCheckBox; 158 159 private JButton scrambleBrainsButton; 160 161 private JButton [ ] directionButtons; 162 163 // 164 165 private boolean isGoblinsMove = true; 166 167 private Rectangle bounds; // animatedComponent bounds 168 169 private Font font; 170 171 ////////////////////////////////////////////////////////////////////// 172 // static methods 173 ////////////////////////////////////////////////////////////////////// 174 175 public static void main ( String [ ] args ) 176 ////////////////////////////////////////////////////////////////////// 177 { 178 JFrame jFrame = new JFrame ( TITLE ); 179 180 try 181 { 182 FrameLib.setIconImage ( jFrame, FRAME_ICON_FILENAME ); 183 } 184 catch ( IOException ex ) 185 { 186 } 187 188 Insight insight = new Insight ( ); 189 190 jFrame.setContentPane ( insight ); 191 192 FrameLib.launchJFrameAsDesktopApp ( 193 jFrame, 194 new Lifecycle [ ] { insight }, 195 FRAME_SIZE, 196 SHUTDOWN_CONFIRMATION_PROMPT ); 197 } 198 199 ////////////////////////////////////////////////////////////////////// 200 ////////////////////////////////////////////////////////////////////// 201 202 public String getAppletInfo ( ) { return INFO; } 203 204 ////////////////////////////////////////////////////////////////////// 205 ////////////////////////////////////////////////////////////////////// 206 207 public synchronized void init ( ) 208 ////////////////////////////////////////////////////////////////////// 209 { 210 System.out.println ( INFO ); 211 212 Container contentPane = getContentPane ( ); 213 214 contentPane.setLayout ( new BorderLayout ( ) ); 215 216 // 217 218 bounds = new Rectangle ( ); 219 220 animatedComponent 221 = new AnimatedComponent ( this, FRAME_RATE ); 222 223 animatedComponent.addComponentListener ( this ); 224 225 animatedComponent.init ( ); 226 227 contentPane.add ( animatedComponent, BorderLayout.CENTER ); 228 229 // 230 231 JPanel southPanel = new JPanel ( new GridBagLayout ( ) ); 232 233 contentPane.add ( southPanel, BorderLayout.SOUTH ); 234 235 // 236 237 JPanel infoPanel = new JPanel ( new GridLayout ( 2, 1 ) ); 238 239 GridBagConstraints gridBagConstraints = new GridBagConstraints ( ); 240 241 gridBagConstraints.weightx = 0.0; 242 243 southPanel.add ( infoPanel, gridBagConstraints ); 244 245 // 246 247 learningOnCheckBox = new JCheckBox ( 248 "Goblin Learning On", goblin_learning_on ); 249 250 learningOnCheckBox.setHorizontalAlignment ( SwingConstants.RIGHT ); 251 252 learningOnCheckBox.setHorizontalTextPosition ( SwingConstants.LEFT ); 253 254 learningOnCheckBox.addItemListener ( this ); 255 256 infoPanel.add ( learningOnCheckBox ); 257 258 // 259 260 scrambleBrainsButton 261 = new JButton ( "Scramble Goblin Brains" ); 262 263 scrambleBrainsButton.addActionListener ( this ); 264 265 infoPanel.add ( scrambleBrainsButton ); 266 267 // 268 269 JPanel brainPanel = new JPanel ( new GridLayout ( 3, 3 ) ); 270 271 gridBagConstraints.weightx = 1.0; 272 273 southPanel.add ( brainPanel, gridBagConstraints ); 274 275 // 276 277 directionButtons = new JButton [ DIRECTIONS.length ]; 278 279 direction_button_element = new int [ DIRECTIONS.length ]; 280 281 direction_button_element [ 1 ] = WALL; 282 283 direction_button_element [ 3 ] = KOBOLD; 284 285 direction_button_element [ 4 ] = GOBLIN; 286 287 for ( int i = 0; i < DIRECTIONS.length; i++ ) 288 { 289 directionButtons [ i ] 290 = new JButton ( 291 ELEMENT_NAME [ direction_button_element [ i ] ] 292 + ": 000%" ); 293 294 if ( i == 4 ) 295 { 296 JLabel goblinLabel = new JLabel ( 297 "Goblin", SwingConstants.CENTER ); 298 299 goblinLabel.setBackground ( ELEMENT_COLOR [ GOBLIN ] ); 300 301 brainPanel.add ( goblinLabel ); 302 } 303 304 brainPanel.add ( directionButtons [ i ] ); 305 306 directionButtons [ i ].addActionListener ( this ); 307 308 Color backgroundColor 309 = ELEMENT_COLOR [ direction_button_element [ i ] ]; 310 311 Color foregroundColor = Color.BLACK; 312 313 if ( backgroundColor == Color.BLACK ) 314 { 315 foregroundColor = Color.WHITE; 316 } 317 318 directionButtons [ i ].setBackground ( backgroundColor ); 319 320 directionButtons [ i ].setForeground ( foregroundColor ); 321 } 322 323 // 324 325 space_contents = new Thing [ BATTLEFIELD_WIDTH ] 326 [ BATTLEFIELD_HEIGHT ]; 327 328 // place outer border walls 329 330 walls = new Thing [ WALL_COUNT ]; 331 332 for ( int i = 0; i < BATTLEFIELD_WIDTH; i++ ) 333 { 334 int x = i; 335 336 int y = 0; 337 338 Thing wall = new Thing ( WALL, x, y ); 339 340 walls [ i ] = wall; 341 342 space_contents [ x ] [ y ] = wall; 343 } 344 345 for ( int i = 0; i < BATTLEFIELD_WIDTH; i++ ) 346 { 347 int x = i; 348 349 int y = BATTLEFIELD_HEIGHT - 1; 350 351 Thing wall = new Thing ( WALL, x, y ); 352 353 walls [ BATTLEFIELD_WIDTH + i ] = wall; 354 355 space_contents [ x ] [ y ] = wall; 356 } 357 358 for ( int i = 0; i < BATTLEFIELD_HEIGHT - 2; i++ ) 359 { 360 int x = 0; 361 362 int y = i + 1; 363 364 Thing wall = new Thing ( WALL, x, y ); 365 366 walls [ 2 * BATTLEFIELD_WIDTH + i ] = wall; 367 368 space_contents [ x ] [ y ] = wall; 369 } 370 371 for ( int i = 0; i < BATTLEFIELD_HEIGHT - 2; i++ ) 372 { 373 int x = BATTLEFIELD_WIDTH - 1; 374 375 int y = i + 1; 376 377 Thing wall = new Thing ( WALL, x, y ); 378 379 walls [ 2 * BATTLEFIELD_WIDTH + BATTLEFIELD_HEIGHT - 2 + i ] 380 = wall; 381 382 space_contents [ x ] [ y ] = wall; 383 } 384 385 // randomly place inner walls 386 387 for ( int i = BORDER_WALL_COUNT; i < WALL_COUNT; i++ ) 388 { 389 putThingInRandomEmptySpace ( 390 walls [ i ] = new Thing ( WALL ) ); 391 } 392 393 // randomly place goblins 394 395 goblins = new Thing [ GOBLIN_COUNT ]; 396 397 for ( int i = 0; i < GOBLIN_COUNT; i++ ) 398 { 399 putThingInRandomEmptySpace ( 400 goblins [ i ] = new Thing ( GOBLIN ) ); 401 } 402 403 // randomly place kobolds 404 405 kobolds = new Thing [ KOBOLD_COUNT ]; 406 407 for ( int i = 0; i < KOBOLD_COUNT; i++ ) 408 { 409 putThingInRandomEmptySpace ( 410 kobolds [ i ] = new Thing ( KOBOLD ) ); 411 } 412 413 // 414 415 goblin_brain_inputs 416 = new Matrix ( ELEMENT_COUNT * DIRECTIONS.length, 1 ); 417 418 goblin_brain = new Backprop_Net ( 419 goblin_brain_inputs.rows, neurons_per_layer, true, true ); 420 } 421 422 public synchronized void start ( ) 423 ////////////////////////////////////////////////////////////////////// 424 { 425 animatedComponent.start ( ); 426 } 427 428 public synchronized void stop ( ) 429 ////////////////////////////////////////////////////////////////////// 430 { 431 animatedComponent.stop ( ); 432 } 433 434 public synchronized void destroy ( ) 435 ////////////////////////////////////////////////////////////////////// 436 { 437 animatedComponent.destroy ( ); 438 } 439 440 ////////////////////////////////////////////////////////////////////// 441 ////////////////////////////////////////////////////////////////////// 442 443 public void update ( JComponent component ) 444 ////////////////////////////////////////////////////////////////////// 445 { 446 if ( isGoblinsMove ) 447 { 448 // Move the goblins 449 450 for ( int i = 0; i < goblins.length; i++ ) 451 { 452 Thing goblin = goblins [ i ]; 453 454 if ( goblin.isAlive ) 455 { 456 moveGoblin ( goblin ); 457 } 458 } 459 460 direction_button_update ( ); 461 462 isGoblinsMove = false; 463 } 464 else 465 { 466 // Move the kobolds 467 468 for ( int i = 0; i < kobolds.length; i++ ) 469 { 470 Thing kobold = kobolds [ i ]; 471 472 if ( kobold.isAlive ) 473 { 474 moveKobold ( kobold ); 475 } 476 } 477 478 isGoblinsMove = true; 479 } 480 481 component.repaint ( ); 482 } 483 484 public void paint ( 485 JComponent component, 486 Graphics2D g ) 487 ////////////////////////////////////////////////////////////////////// 488 { 489 component.getBounds ( bounds ); 490 491 // paint the background 492 493 g.setColor ( Color.BLACK ); 494 495 g.fillRect ( bounds.x, bounds.y, bounds.width, bounds.height ); 496 497 // paint the kobolds, goblins, and walls 498 499 plotThings ( bounds, g, ELEMENT_COLOR [ KOBOLD ], kobolds ); 500 501 plotThings ( bounds, g, ELEMENT_COLOR [ GOBLIN ], goblins ); 502 503 plotThings ( bounds, g, ELEMENT_COLOR [ WALL ], walls ); 504 505 // paint the status 506 507 g.setColor ( java.awt.Color.white ); 508 509 g.setFont ( font ); 510 511 g.drawString ( 512 createStatusString ( 513 kobolds_killed, 514 goblins_killed, 515 kobolds_killed_by_goblins, 516 goblins_killed_by_kobolds ), 517 bounds.x + 10, bounds.y + bounds.height - 10 ); 518 } 519 520 ////////////////////////////////////////////////////////////////////// 521 ////////////////////////////////////////////////////////////////////// 522 523 public void itemStateChanged ( ItemEvent itemEvent ) 524 ////////////////////////////////////////////////////////////////////// 525 { 526 Object item = itemEvent.getItem ( ); 527 528 if ( item == learningOnCheckBox ) 529 { 530 goblin_learning_on = learningOnCheckBox.isSelected ( ); 531 532 resetStats ( ); 533 } 534 } 535 536 public void actionPerformed ( ActionEvent actionEvent ) 537 ////////////////////////////////////////////////////////////////////// 538 { 539 Object source = actionEvent.getSource ( ); 540 541 if ( source == scrambleBrainsButton ) 542 { 543 goblin_brain.weights_randomize_uniform ( -1.0, 1.0 ); 544 545 resetStats ( ); 546 } 547 else if ( source instanceof JButton ) 548 { 549 int buttonIndex = -1; 550 551 JButton button = null; 552 553 for ( int i = 0; i < directionButtons.length; i++ ) 554 { 555 if ( source == directionButtons [ i ] ) 556 { 557 buttonIndex = i; 558 559 button = ( JButton ) source; 560 561 break; 562 } 563 } 564 565 direction_button_element [ buttonIndex ]++; 566 567 if ( direction_button_element [ buttonIndex ] >= ELEMENT_COUNT ) 568 { 569 direction_button_element [ buttonIndex ] = 0; 570 } 571 572 Color backgroundColor 573 = ELEMENT_COLOR [ direction_button_element [ buttonIndex ] ]; 574 575 Color foregroundColor = Color.BLACK; 576 577 if ( backgroundColor == Color.BLACK ) 578 { 579 foregroundColor = Color.WHITE; 580 } 581 582 button.setBackground ( backgroundColor ); 583 584 button.setForeground ( foregroundColor ); 585 586 button.setText ( 587 "" + ELEMENT_NAME [ direction_button_element [ 588 buttonIndex ] ] ); 589 } 590 } 591 592 ////////////////////////////////////////////////////////////////////// 593 // interface ComponentListener methods 594 ////////////////////////////////////////////////////////////////////// 595 596 public void componentResized ( ComponentEvent componentEvent ) 597 ////////////////////////////////////////////////////////////////////// 598 { 599 Object source = componentEvent.getSource ( ); 600 601 if ( source == animatedComponent ) 602 { 603 Rectangle bounds = animatedComponent.getBounds ( ); 604 605 FontLib.setMaxFont ( 606 animatedComponent, 607 createStatusString ( 99999, 99999, 99999, 99999 ), 608 FONT_NAME, 609 FONT_STYLE, 610 bounds.width, 611 bounds.height ); 612 } 613 } 614 615 public void componentHidden ( ComponentEvent componentEvent ) 616 ////////////////////////////////////////////////////////////////////// 617 { 618 } 619 620 public void componentMoved ( ComponentEvent componentEvent ) 621 ////////////////////////////////////////////////////////////////////// 622 { 623 } 624 625 public void componentShown ( ComponentEvent componentEvent ) 626 ////////////////////////////////////////////////////////////////////// 627 { 628 } 629 630 ////////////////////////////////////////////////////////////////////// 631 // private methods 632 ////////////////////////////////////////////////////////////////////// 633 634 private void resetStats ( ) 635 ////////////////////////////////////////////////////////////////////// 636 { 637 kobolds_killed = 0; 638 639 goblins_killed = 0; 640 641 kobolds_killed_by_goblins = 0; 642 643 goblins_killed_by_kobolds = 0; 644 } 645 646 private static String createStatusString ( 647 long kobolds_killed, 648 long goblins_killed, 649 long kobolds_killed_by_goblins, 650 long goblins_killed_by_kobolds ) 651 ////////////////////////////////////////////////////////////////////// 652 { 653 return "Dead Kobolds: " 654 + kobolds_killed 655 + " " 656 + "Dead Goblins: " + goblins_killed 657 + " " 658 + "Kobolds killed by Goblins: " + kobolds_killed_by_goblins 659 + " " 660 + "Goblins killed by Kobolds: " + goblins_killed_by_kobolds; 661 } 662 663 private void direction_button_update ( ) 664 ////////////////////////////////////////////////////////////////////// 665 { 666 Matrix direction_probabilities 667 = calculateDirectionProbabilities ( 668 direction_button_element ); 669 670 for ( int i = 0; i < directionButtons.length; i++ ) 671 { 672 directionButtons [ i ].setText ( 673 "" + ELEMENT_NAME [ direction_button_element [ i ] ] 674 + ": " 675 + ( int ) ( direction_probabilities.data [ i ] [ 0 ] 676 * 100.0 ) + "%" ); 677 } 678 } 679 680 private Matrix calculateDirectionProbabilities ( 681 int [ ] typeVision ) 682 ////////////////////////////////////////////////////////////////////// 683 { 684 for ( int i = 0; i < typeVision.length; i++ ) 685 { 686 for ( int j = 0; j < ELEMENT_COUNT; j++ ) 687 { 688 goblin_brain_inputs.data 689 [ ELEMENT_COUNT * i + j ] [ 0 ] 690 = ( typeVision [ i ] == j ) ? 1.0 : 0.0; 691 } 692 } 693 694 Matrix leanings = goblin_brain.forward_pass ( goblin_brain_inputs ); 695 696 return leanings.divide ( leanings.sum ( ) ); 697 } 698 699 private Point goblin_move_direction ( Thing goblin ) 700 ////////////////////////////////////////////////////////////////////// 701 { 702 int [ ] typeVision = new int [ DIRECTIONS.length ]; 703 704 for ( int i = 0; i < typeVision.length; i++ ) 705 { 706 typeVision [ i ] = ETHER; 707 708 int x = goblin.x + DIRECTIONS [ i ].x; 709 710 int y = goblin.y + DIRECTIONS [ i ].y; 711 712 if ( ( x >= 0 ) 713 && ( x < space_contents.length ) 714 && ( y >= 0 ) 715 && ( y < space_contents [ 0 ].length ) ) 716 { 717 Thing content = space_contents [ x ] [ y ]; 718 719 if ( content != null ) 720 { 721 typeVision [ i ] = content.type; 722 } 723 } 724 } 725 726 Matrix direction_probabilities 727 = calculateDirectionProbabilities ( typeVision ); 728 729 double sum = 0.0; 730 731 double r = Croft_Random.uniform ( 0.0, 1.0 ); 732 733 for ( int i = 0; i < direction_probabilities.rows; i++ ) 734 { 735 if ( r < sum + direction_probabilities.data [ i ] [ 0 ] ) 736 { 737 goblin_best_direction = i; 738 739 break; 740 } 741 742 sum += direction_probabilities.data [ i ] [ 0 ]; 743 } 744 745 return new Point ( 746 DIRECTIONS [ goblin_best_direction ].x, 747 DIRECTIONS [ goblin_best_direction ].y ); 748 } 749 750 private void goblin_move_reinforce ( double target ) 751 ////////////////////////////////////////////////////////////////////// 752 { 753 Matrix desired_directions = new Matrix ( 754 goblin_brain.outputs.m [ goblin_brain.layers - 1 ] ); 755 756 if ( goblin_learning_on ) 757 { 758 // why are we using supervised instead of reinforcement learning? 759 760 for ( int i = 0; i < DIRECTIONS.length; i++ ) 761 { 762 desired_directions.data [ i ] [ 0 ] = 1.0 - target; 763 } 764 765 desired_directions.data [ goblin_best_direction ] [ 0 ] = target; 766 767 goblin_brain.backward_pass ( desired_directions ); 768 769 goblin_brain.weights_update ( ); 770 } 771 } 772 773 private void moveGoblin ( Thing goblin ) 774 ////////////////////////////////////////////////////////////////////// 775 { 776 Point move_direction = goblin_move_direction ( goblin ); 777 778 int x = goblin.x + move_direction.x; 779 780 int y = goblin.y + move_direction.y; 781 782 boolean abortMove 783 = ( x < 0 ) 784 || ( x >= BATTLEFIELD_WIDTH ) 785 || ( y < 0 ) 786 || ( y >= BATTLEFIELD_HEIGHT ); 787 788 if ( !abortMove ) 789 { 790 Thing content = space_contents [ x ] [ y ]; 791 792 if ( content == null ) 793 { 794 } 795 else if ( content.type == KOBOLD ) 796 { 797 killThing ( content ); 798 799 goblin_move_reinforce ( 1.0 ); 800 801 kobolds_killed_by_goblins++; 802 } 803 else if ( content.type == GOBLIN ) 804 { 805 killThing ( content ); 806 807 goblin_move_reinforce ( 0.0 ); 808 } 809 else if ( content.type == WALL ) 810 { 811 killThing ( goblin ); 812 813 goblin_move_reinforce ( 0.0 ); 814 815 abortMove = true; 816 } 817 else 818 { 819 throw new RuntimeException ( "unknown Thing type" ); 820 } 821 } 822 823 if ( !abortMove ) 824 { 825 space_contents [ goblin.x ] [ goblin.y ] = null; 826 827 goblin.x = x; 828 829 goblin.y = y; 830 831 space_contents [ x ] [ y ] = goblin; 832 } 833 } 834 835 private void moveKobold ( Thing kobold ) 836 ////////////////////////////////////////////////////////////////////// 837 { 838 // move randomly 839 840 int rd = ( int ) RandomLib.roll ( 1, 8, -1 ); 841 842 Point move_direction 843 = new Point ( DIRECTIONS [ rd ].x, DIRECTIONS [ rd ].y ); 844 845 int x = kobold.x + move_direction.x; 846 847 int y = kobold.y + move_direction.y; 848 849 boolean abortMove 850 = ( x < 0 ) 851 || ( x >= BATTLEFIELD_WIDTH ) 852 || ( y < 0 ) 853 || ( y >= BATTLEFIELD_HEIGHT ); 854 855 if ( !abortMove ) 856 { 857 Thing content = space_contents [ x ] [ y ]; 858 859 if ( content == null ) 860 { 861 } 862 else if ( content.type == GOBLIN ) 863 { 864 killThing ( content ); 865 866 goblins_killed_by_kobolds++; 867 } 868 else if ( content.type == KOBOLD ) 869 { 870 killThing ( content ); 871 } 872 else if ( content.type == WALL ) 873 { 874 killThing ( kobold ); 875 876 abortMove = true; 877 } 878 else 879 { 880 throw new RuntimeException ( "unknown Thing type" ); 881 } 882 } 883 884 if ( !abortMove ) 885 { 886 space_contents [ kobold.x ] [ kobold.y ] = null; 887 888 kobold.x = x; 889 890 kobold.y = y; 891 892 space_contents [ x ] [ y ] = kobold; 893 } 894 } 895 896 private void killThing ( Thing thing ) 897 ////////////////////////////////////////////////////////////////////// 898 { 899 if ( thing.type == GOBLIN ) 900 { 901 goblins_killed++; 902 } 903 else if ( thing.type == KOBOLD ) 904 { 905 kobolds_killed++; 906 } 907 908 space_contents [ thing.x ] [ thing.y ] = null; 909 910 // spawn a replacement 911 912 // why do we bother with isAlive flag? 913 914 putThingInRandomEmptySpace ( thing ); 915 } 916 917 private void putThingInRandomEmptySpace ( Thing thing ) 918 ////////////////////////////////////////////////////////////////////// 919 { 920 int x; 921 922 int y; 923 924 while ( true ) 925 { 926 x = ( int ) RandomLib.roll ( 1, BATTLEFIELD_WIDTH , -1 ); 927 928 y = ( int ) RandomLib.roll ( 1, BATTLEFIELD_HEIGHT, -1 ); 929 930 if ( space_contents [ x ] [ y ] == null ) 931 { 932 break; 933 } 934 } 935 936 space_contents [ x ] [ y ] = thing; 937 938 thing.x = x; 939 940 thing.y = y; 941 } 942 943 private void plotThings ( 944 Rectangle bounds, 945 Graphics graphics, 946 Color color, 947 Thing [ ] things ) 948 ////////////////////////////////////////////////////////////////////// 949 { 950 if ( things == null ) 951 { 952 return; 953 } 954 955 for ( int i = 0; i < things.length; i++ ) 956 { 957 Thing thing = things [ i ]; 958 959 // Since they regenerate, is isAlive always true? 960 961 if ( thing.isAlive ) 962 { 963 PlotLib.xy ( color, thing.x + 0.5, thing.y + 0.5, 964 bounds, graphics, 965 0, BATTLEFIELD_WIDTH, 0, BATTLEFIELD_HEIGHT, 1, true ); 966 } 967 } 968 } 969 970 ////////////////////////////////////////////////////////////////////// 971 ////////////////////////////////////////////////////////////////////// 972 }