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         }