001 package com.croftsoft.apps.dice; 002 003 import java.awt.Graphics; 004 import java.awt.*; 005 import java.util.*; 006 import java.lang.Math; 007 import java.awt.Color; 008 import java.awt.Point; 009 import java.awt.Rectangle; 010 import java.util.*; 011 import javax.swing.*; 012 013 import com.croftsoft.core.lang.lifecycle.Lifecycle; 014 import com.croftsoft.core.animation.*; 015 import com.croftsoft.core.animation.component.*; 016 017 /********************************************************************* 018 * Artificial neural network perceptron demonstration. 019 * 020 * @version 021 * 2002-03-23 022 * @since 023 * 1996-08-23 024 * @author 025 * <a href="https://www.croftsoft.com/">David Wallace Croft</a> 026 *********************************************************************/ 027 028 public class Dice 029 extends JApplet 030 implements Lifecycle, ComponentAnimator 031 ////////////////////////////////////////////////////////////////////// 032 ////////////////////////////////////////////////////////////////////// 033 { 034 035 private static final String VERSION = "2002-03-13"; 036 037 private static final String APPLET_TITLE 038 = "Dice"; 039 040 private static final String INFO 041 = "\n" + APPLET_TITLE + "\n" 042 + "Copyright 2002 CroftSoft Inc\n" 043 + "https://www.croftsoft.com/\n" 044 + "Version " + VERSION + "\n"; 045 046 private static final int FRAME_RATE = 24; 047 048 private static final int text_x = 10; 049 private static final int text_y = 20; // distance between lines 050 051 // 052 053 private AnimatedComponent animatedComponent; 054 055 private Rectangle bounds = new Rectangle ( ); 056 057 // 058 059 private int start_fights = 2000; 060 private int fights_max = 30; 061 062 private Random random = new Random ( ); 063 064 private long damage; 065 private long count; 066 private long sum; 067 private double mean; 068 069 private NPC npc1 = new NPC ( "Combatant_1" ); 070 private NPC npc2 = new NPC ( "Combatant_2" ); 071 072 private int fights = 0; 073 private int fights_finished = 0; 074 private long round_num = 0; 075 076 private long hp_max = 100; 077 078 private Point fight_history [ ] = new Point [ fights_max ]; 079 private boolean winloss_history [ ] = new boolean [ fights_max ]; 080 081 private Rectangle r = new Rectangle ( text_x, 8 * text_y, 200, 200 ); 082 083 private double w_ac = 100.0; 084 private double w_hp = 20.0; 085 private double bias = 1000.0; 086 private double w_bias = -50.0 * 20.0 / bias; 087 private double learn_rate = 1.0; 088 // private double win_prob = 0.5; 089 private boolean win_predicted = true; 090 091 ////////////////////////////////////////////////////////////////////// 092 ////////////////////////////////////////////////////////////////////// 093 094 public String getAppletInfo ( ) { return INFO; } 095 096 ////////////////////////////////////////////////////////////////////// 097 ////////////////////////////////////////////////////////////////////// 098 099 public synchronized void init ( ) 100 ////////////////////////////////////////////////////////////////////// 101 { 102 System.out.println ( INFO ); 103 104 animatedComponent = new AnimatedComponent ( this, FRAME_RATE ); 105 106 animatedComponent.init ( ); 107 108 setContentPane ( animatedComponent ); 109 110 npc2.combat_stats.ac = 0; 111 112 npc2.combat_stats.hp = 50; 113 114 update_fight_history ( npc2.combat_stats.ac, npc2.combat_stats.hp ); 115 } 116 117 public synchronized void start ( ) 118 ////////////////////////////////////////////////////////////////////// 119 { 120 animatedComponent.start ( ); 121 } 122 123 public synchronized void stop ( ) 124 ////////////////////////////////////////////////////////////////////// 125 { 126 animatedComponent.stop ( ); 127 } 128 129 public synchronized void destroy ( ) 130 ////////////////////////////////////////////////////////////////////// 131 { 132 animatedComponent.destroy ( ); 133 } 134 135 ////////////////////////////////////////////////////////////////////// 136 ////////////////////////////////////////////////////////////////////// 137 138 public void update ( JComponent component ) 139 ////////////////////////////////////////////////////////////////////// 140 { 141 fight ( ); 142 143 component.repaint ( ); 144 } 145 146 public void paint ( 147 JComponent component, 148 Graphics2D graphics ) 149 ////////////////////////////////////////////////////////////////////// 150 { 151 component.getBounds ( bounds ); 152 153 graphics.setColor ( Color.white ); 154 155 graphics.fillRect ( 156 bounds.x, bounds.y, bounds.width, bounds.height ); 157 158 graphics.setColor ( Color.black ); 159 160 graphics.drawString ( "Round " + round_num, text_x, text_y ); 161 162 graphics.drawString ( npc1.toString ( ) , text_x, 2 * text_y ); 163 164 graphics.drawString ( npc2.toString ( ) , text_x, 3 * text_y ); 165 166 graphics.drawString ( 167 "Y = W_AC * AC + W_HP * HP + W_BIAS * BIAS", text_x, 4 * text_y ); 168 169 double weighted_sum = w_ac * fight_history [ fights - 1 ].x 170 + w_hp * fight_history [ fights - 1 ].y + w_bias * bias; 171 172 graphics.drawString ( " = " + w_ac + " * " + fight_history [ fights - 1 ].x 173 + " + " + w_hp + " * " + fight_history [ fights - 1 ].y 174 + " + " + w_bias + " * " + bias + " = " + weighted_sum, text_x, 5 * text_y ); 175 176 if ( round_num == 0 ) 177 { 178 graphics.drawString ( "Combat begins.", text_x, 6 * text_y ); 179 } 180 else if ( ( npc1.combat_stats.hp > 0 ) && ( npc2.combat_stats.hp <= 0 ) ) 181 { 182 graphics.drawString ( npc1.name + " wins!", text_x, 6 * text_y ); 183 } 184 else if ( ( npc2.combat_stats.hp > 0 ) && ( npc1.combat_stats.hp <= 0 ) ) 185 { 186 graphics.drawString ( npc2.name + " wins!", text_x, 6 * text_y ); 187 } 188 else 189 { 190 graphics.drawString ( "Combat continues.", text_x, 6 * text_y ); 191 } 192 193 graphics.drawString ( "Win predicted: " + win_predicted, text_x, 7 * text_y ); 194 195 // graphics.drawString ( "Win confidence: " + win_prob, text_x, 7 * text_y ); 196 197 graphics.drawRect ( r.x, r.y, r.width, r.height ); 198 199 plot_Line ( -w_ac / w_hp, -w_bias * bias / w_hp, 200 r, graphics, -10, 10, 0, hp_max ); 201 202 plot_ac_hp ( r, graphics ); 203 } 204 205 public void update_fight_history ( long ac, long hp ) 206 ////////////////////////////////////////////////////////////////////// 207 { 208 if ( fights == fights_max ) { 209 for ( int index_fight = 0; index_fight < fights - 1; index_fight++ ) { 210 fight_history [ index_fight ] = fight_history [ index_fight + 1 ]; 211 winloss_history [ index_fight ] 212 = winloss_history [ index_fight + 1 ]; 213 } 214 } else { 215 fights++; 216 } 217 fight_history [ fights - 1 ] = new Point ( ( int ) ac, ( int ) hp ); 218 } 219 220 public void fight ( ) { 221 ////////////////////////////////////////////////////////////////////// 222 if ( ( npc1.combat_stats.hp > 0 ) && ( npc2.combat_stats.hp <= 0 ) ) { 223 update_winloss_history ( true ); 224 // paint_buffered ( ); 225 npc1 = new NPC ( "Combatant_1", npc1.xp + 1 ); 226 npc2 = new NPC ( "Combatant_2", npc2.xp ); 227 prep_next_fight ( ); 228 } else if ( ( npc2.combat_stats.hp > 0 ) && ( npc1.combat_stats.hp <= 0 ) ) { 229 update_winloss_history ( false ); 230 // paint_buffered ( ); 231 npc1 = new NPC ( "Combatant_1", npc1.xp ); 232 npc2 = new NPC ( "Combatant_2", npc2.xp + 1 ); 233 prep_next_fight ( ); 234 } else if ( round_num >= 0 ) { 235 round ( npc1.combat_stats, npc2.combat_stats ); 236 } 237 round_num++; 238 /* 239 if ( round_num >= 0 ) { 240 paint_buffered ( ); 241 } 242 */ 243 } 244 245 public boolean attacker_hits ( 246 long attacker_adjusted_thac0, 247 long defender_adjusted_AC ) { 248 ////////////////////////////////////////////////////////////////////// 249 long to_hit_roll = roll ( 1, 20, 0 ); 250 if ( to_hit_roll == 20 ) return true; 251 if ( attacker_adjusted_thac0 - to_hit_roll <= defender_adjusted_AC ) 252 return true; 253 return false; 254 } 255 256 public long attack ( 257 Combat_Stats attacker_Combat_Stats, 258 Combat_Stats defender_Combat_Stats ) { 259 ////////////////////////////////////////////////////////////////////// 260 // Returns the damage for the attack (0 if a miss). 261 ////////////////////////////////////////////////////////////////////// 262 if ( !attacker_hits ( 263 attacker_Combat_Stats.thac0, 264 defender_Combat_Stats.ac ) ) return 0; 265 return roll ( attacker_Combat_Stats.damage_multiplier, 266 attacker_Combat_Stats.damage_base, 267 attacker_Combat_Stats.damage_bonus ); 268 } 269 270 public void plot_ac_hp ( Rectangle r, Graphics g ) { 271 ////////////////////////////////////////////////////////////////////// 272 Color winloss_color; 273 ////////////////////////////////////////////////////////////////////// 274 for ( int index_fight = 0; index_fight < fights - 1; index_fight++ ) { 275 if ( winloss_history [ index_fight ] ) 276 winloss_color = java.awt.Color.green; 277 else 278 winloss_color = java.awt.Color.red; 279 plot_xy ( winloss_color, 280 ( double ) fight_history [ index_fight ].x, 281 ( double ) fight_history [ index_fight ].y, 282 r, g, -10, 10, 0, hp_max ); 283 } 284 plot_xy ( java.awt.Color.blue, 285 ( double ) fight_history [ fights - 1 ].x, 286 ( double ) fight_history [ fights - 1 ].y, 287 r, g, -10, 10, 1, 100 ); 288 } 289 290 public void plot_Line ( 291 double m, 292 double b, 293 Rectangle r, 294 Graphics g, 295 double x0, 296 double x1, 297 double y0, 298 double y1 ) { 299 ////////////////////////////////////////////////////////////////////// 300 double x_scale = ( double ) r.width / ( double ) ( x1 - x0 ); 301 double y_scale = ( double ) r.height / ( double ) ( y1 - y0 ); 302 ////////////////////////////////////////////////////////////////////// 303 g.drawLine ( r.x, 304 r.y + r.height - ( int ) ( ( m * x0 + b - y0 ) * y_scale ), 305 r.x + r.width , 306 r.y + r.height - ( int ) ( ( m * x1 + b - y0 ) * y_scale ) ); 307 } 308 309 public void plot_xy ( 310 Color c, 311 double x, 312 double y, 313 Rectangle r, 314 Graphics g, 315 double x0, 316 double x1, 317 double y0, 318 double y1 ) { 319 ////////////////////////////////////////////////////////////////////// 320 double x_scale = ( double ) r.width / ( double ) ( x1 - x0 ); 321 double y_scale = ( double ) r.height / ( double ) ( y1 - y0 ); 322 double oval_size = ( x_scale < y_scale ) ? x_scale : y_scale; 323 oval_size = ( oval_size > 4 ) ? oval_size : 4; 324 325 Color c_old = g.getColor ( ); 326 g.setColor ( c ); 327 g.fillOval ( r.x + ( int ) ( ( x - x0 ) * x_scale ), 328 r.y + r.height - ( int ) ( ( y - y0 ) * y_scale ), 329 ( int ) oval_size, ( int ) oval_size ); 330 g.setColor ( c_old ); 331 } 332 333 public void prep_next_fight ( ) { 334 ////////////////////////////////////////////////////////////////////// 335 npc2.combat_stats.ac = roll ( 1, 21, -11 ); 336 npc2.combat_stats.hp = roll ( 1, hp_max, 0 ); 337 update_fight_history ( npc2.combat_stats.ac, npc2.combat_stats.hp ); 338 // win_prob = sigmoid ( ( w_ac * npc2.combat_stats.ac 339 // + w_hp * npc2.combat_stats.hp + w_bias * bias ) / 1000.0 ); 340 if ( w_ac * npc2.combat_stats.ac 341 + w_hp * npc2.combat_stats.hp + w_bias * bias >= 0.0 ) 342 win_predicted = true; 343 else 344 win_predicted = false; 345 round_num = -1; 346 } 347 348 public long roll ( 349 long multiplier, 350 long base, 351 long offset ) { 352 ////////////////////////////////////////////////////////////////////// 353 long temp = 0; 354 ////////////////////////////////////////////////////////////////////// 355 for ( long index_roll = 0; index_roll < multiplier; index_roll++ ) { 356 temp += 1 + Math.round ( 357 ( double ) ( base - 1 ) * random.nextDouble ( ) ); 358 } 359 return temp + offset; 360 } 361 362 public boolean round ( 363 Combat_Stats combatant_1_Combat_Stats, 364 Combat_Stats combatant_2_Combat_Stats ) { 365 ////////////////////////////////////////////////////////////////////// 366 // Initiative 50%/50% chance for each. 367 // Returns true if both combatants are still up. 368 ////////////////////////////////////////////////////////////////////// 369 if ( roll ( 1, 2, 0 ) == 1 ) { 370 if ( combatant_1_Combat_Stats.hp > 0 ) { 371 combatant_2_Combat_Stats.hp 372 -= attack ( combatant_1_Combat_Stats, combatant_2_Combat_Stats ); 373 } 374 if ( combatant_2_Combat_Stats.hp > 0 ) { 375 combatant_1_Combat_Stats.hp 376 -= attack ( combatant_2_Combat_Stats, combatant_1_Combat_Stats ); 377 } 378 } else { 379 if ( combatant_2_Combat_Stats.hp > 0 ) { 380 combatant_1_Combat_Stats.hp 381 -= attack ( combatant_2_Combat_Stats, combatant_1_Combat_Stats ); 382 } 383 if ( combatant_1_Combat_Stats.hp > 0 ) { 384 combatant_2_Combat_Stats.hp 385 -= attack ( combatant_1_Combat_Stats, combatant_2_Combat_Stats ); 386 } 387 } 388 return ( ( combatant_1_Combat_Stats.hp > 0 ) 389 && ( combatant_2_Combat_Stats.hp > 0 ) ); 390 } 391 392 // public double sigmoid ( double a ) { 393 // ////////////////////////////////////////////////////////////////////// 394 // return 1.0 / ( 1.0 + java.lang.Math.exp ( -a ) ); 395 // } 396 397 public void update_winloss_history ( boolean is_win ) { 398 ////////////////////////////////////////////////////////////////////// 399 double delta; 400 ////////////////////////////////////////////////////////////////////// 401 winloss_history [ fights - 1 ] = is_win; 402 // if ( is_win ) delta = -learn_rate * ( win_prob - 1.0 ) * ( win_prob ) * ( 1.0 - win_prob ); 403 // else delta = -learn_rate * ( win_prob - 0.0 ) * ( win_prob ) * ( 1.0 - win_prob ); 404 if ( is_win && !win_predicted ) { 405 delta = learn_rate; 406 } else if ( !is_win && win_predicted ) { 407 delta = -learn_rate; 408 } else 409 delta = 0.0; 410 fights_finished++; 411 delta = delta / fights_finished; 412 w_ac += delta * ( double ) fight_history [ fights - 1 ].x; 413 w_hp += delta * ( double ) fight_history [ fights - 1 ].y; 414 w_bias += delta * bias; 415 } 416 417 ////////////////////////////////////////////////////////////////////// 418 ////////////////////////////////////////////////////////////////////// 419 }