###A loong long time ago I asked [this question](http://codereview.stackexchange.com/q/2631/3163) which has since garnered about one thousand views.

_____

#Now for something new

The demo for what I have is located here: http://jsfiddle.net/maniator/ueceeq7k/27/

I am generally happy with what I have built (although I did not implement all of the visuals (yet) of the original).

I want to know if there is some improvements to my hitting algorithm that can be made, and maybe some suggestions of how to implement other fielders (aside from the pitcher that is already written)

The javascript code is as follows: 

    const logger = ((logArea, logDelay = 0) => {
    	const playArea = document.getElementById(logArea);
    	return {
    		delay: logDelay,
    		log(...args) {
    			let div = document.createElement('div');
    
    			// Can see the result in the console
    			console.log(...args);
    
    			setTimeout(() => {
    				// append to the top of the log
    				playArea.insertBefore(div, playArea.firstChild);
    				div.textContent = args.join(', ')
    			}, this.delay);
    
    			this.delay += logDelay;
    		},
    		logPad(...args) {
    			this.log();
    			this.log(...args);
    		}
    	};
    })('playArea', 120);
    
    const Team = (() => {
    	const numberOfPlayers = 9;
    
    	return class Team {
    		constructor(name) {
    			this.name = name;
    			this.players = [];
    			this.listeners = [];
    			this.playerIndex = 0;
    			this.score = 0;
    			this.generatePlayers();
    		}
    
    		toString() {
    			return this.name;
    		}
    
    		gainRun() {
    			this.score++;
    			logger.log(`${this} now have ${this.score} runs`);
    		}
    
    		generatePlayers() {
    			for (let i = 0; i < numberOfPlayers; ++i) {
    				this.players.push(new Player(`${this} Player ${i + 1}`, this));
    			}
    
    			this.pitcher = new Pitcher(`${this} Pitcher`, this);
    		}
    
    		nextPlayer() {
    			this.playerIndex %= numberOfPlayers;
    
    			let nextPlayer = this.players[this.playerIndex++];
    
    			logger.log(`${nextPlayer} is up to bat`);
    
    			return nextPlayer;
    		}
    
    		pitchTo(otherTeam) {
    			let outs = 0;
    			while (outs < 3) {
    				let nextPlayer = otherTeam.nextPlayer();
    				let strikes = 0;
    				let balls = 0;
    
    				while (strikes < 3 && balls < 4) {
    					let pitch = this.pitcher.pitch();
    					let hit = nextPlayer.acceptPitch(strikes, balls);
    
    					if (hit.out) {
    						outs++;
    
    						logger.log(`${nextPlayer} hit the ball!`);
    						logger.log(`${nextPlayer} hit a ${hit.name}`);
    						break;
    					}
    
    					if (hit.bases === 0) {
    						if (hit.strike) {
    							strikes++;
    							logger.log(`${nextPlayer} swung and missed`);
    						} else {
    							if (pitch.strike) {
    								strikes++;
    							} else {
    								balls++;
    							}
    							logger.log(`${nextPlayer} waited and got a ${pitch.name}`);
    						}
    					} else {
    						logger.log(`${nextPlayer} hit the ball!`);
    						this.trigger('Hit', nextPlayer, hit);
    						logger.log(`${nextPlayer} got a ${hit.name}`);
    						break;
    					}
    				}
    
    				if (strikes >= 3) {
    					outs++;
    					logger.log(`${nextPlayer} struck out`);
    				} else if (balls >= 4) {
    					logger.log(`${nextPlayer} walked`);
    					this.trigger('Hit', nextPlayer, {
    						bases: 1,
    					});
    				}
    			}
    		}
    
    		addListener(object) {
    			if (!!object.trigger) {
    				this.listeners.push(object);
    			}
    		}
    
    		trigger() {
    			this.listeners.forEach((listener) => listener.trigger(...arguments))
    		}
    	};
    })();
    
    class Player {
    	constructor(name, team) {
    		this.name = name;
    		this.team = team;
    	}
    
    	toString() {
    		return this.name;
    	}
    
    	acceptPitch(strikes = 0, balls = 0) {
    		const random = Math.floor(Math.random() * 1000);
    		const swingRate = 500 - (75 * strikes) + (30 * balls);
    
    		if (random < swingRate) {
    			return {
    				name: 'swing and a miss',
    				bases: 0,
    				out: false,
    				strike: true,
    			};
    		} else if (random < 880) { // wait for the pitch or hit into an out
    			if (random > 830) {
    				return {
    					name: 'fly out',
    					bases: 0,
    					out: true,
    				};
    			} else if (random > 810) {
    				return {
    					name: 'ground out',
    					bases: 0,
    					out: true,
    				};
    			}
    
    			return {
    				name: 'wait',
    				bases: 0,
    				out: false,
    			};
    		} else if (random <= 1000) { // hit the ball
    			if (random > 940 && random < 970) {
    				return {
    					name: 'double',
    					bases: 2,
    					out: false,
    				};
    			}
    			else if (random >= 970 && random < 995) {
    				return {
    					name: 'triple',
    					bases: 3,
    					out: false,
    				};
    			}
    			else if (random >= 995) {
    				return {
    					name: 'homerun',
    					bases: 4,
    					out: false,
    				};
    			} else {
    				return {
    					name: 'single',
    					bases: 1,
    					out: false,
    				};
    			}
    		}
    	}
    }
    ;
    
    class Pitcher extends Player {
    	static get pitchTypes() {
    		// @todo math this SH*T up
    		return [{
    			name: 'strike',
    			strike: true,
    		}, {
    			name: 'ball',
    			strike: false,
    		}, {
    			name: 'strike',
    			strike: true,
    		},];
    	}
    
    	pitch() {
    		const types = Pitcher.pitchTypes;
    		const random = Math.floor(Math.random() * (types).length);
    		logger.log(`${this} threw the ball`);
    		return types[random];
    	}
    }
    ;
    
    const Stadium = ((diamondArea) => {
    	const diamondDiv = document.getElementById(diamondArea);
    	let firstBase = false,
    		secondBase = false,
    		thirdBase = false,
    		home = false;
    
    	return class Stadium {
    		constructor(a, b) {
    			// @todo checks that a and b are team objects
    			this.teams = [a, b];
    		}
    
    		set firstBase(player) {
    			if (firstBase && player) {
    				logger.log(`${firstBase} ran to second`);
    				this.secondBase = firstBase;
    			}
    			firstBase = player;
    		}
    
    		set secondBase(player) {
    			if (secondBase && player) {
    				logger.log(`${secondBase} ran to third`);
    				this.thirdBase = secondBase;
    			}
    			secondBase = player;
    		}
    
    		set thirdBase(player) {
    			if (thirdBase && player) {
    				logger.log(`${thirdBase} ran home`);
    				this.home = thirdBase;
    			}
    			thirdBase = player;
    		}
    
    		set home(player) {
    			player.team.gainRun();
    		}
    
    		single(player) {
    			this.firstBase = player;
    			logger.log(`${player} ran to first base`);
    		}
    
    		double(player) {
    			this.single(player);
    			this.secondBase = player;
    			this.firstBase = false;
    			logger.log(`${player} ran to second base`);
    		}
    
    		triple(player) {
    			this.double(player);
    			this.thirdBase = player;
    			this.secondBase = false;
    			logger.log(`${player} ran to third base`);
    		}
    
    		homerun(player) {
    			this.triple(player);
    			this.home = player;
    			this.thirdBase = false;
    			logger.log(`${player} ran to home base`);
    		}
    
    		get baseTypes() {
    			return [this.homerun, this.single, this.double, this.triple];
    		}
    
    		clear() {
    			firstBase = secondBase = thirdBase = false;
    
    			return this;
    		}
    
    		playGame(innings) {
    			let extraInnings = 0;
    			let [teamA, teamB] = this.teams;
    			logger.log(`${innings} inning game!`);
    			this.listenToTeamActions();
    			for (let i = 1; i <= innings; ++i) {
    				logger.logPad(`Playing inning ${i} of ${innings}`);
    				this.playInning(i);
    			}
    
    			while (teamA.score === teamB.score) {
    				extraInnings++;
    				logger.logPad(`EXTRA INNINGS!`);
    				logger.logPad(`Score: ${teamA}: ${teamA.score} to ${teamB}: ${teamB.score}`);
    
    				this.playInning(`EXTRA ${extraInnings}`);
    			}
    
    			logger.logPad(`GAME OVER${extraInnings ? ` WITH ${extraInnings} EXTRA INNINGS!` : '!'}`);
    			logger.logPad(`Score: ${teamA}: ${teamA.score} to ${teamB}: ${teamB.score}`);
    		}
    
    		playInning(index) {
    			let [teamA, teamB] = this.teams;
    
    			logger.logPad(`${teamB} are up to bat in inning ${index}`);
    			teamA.pitchTo(teamB);
    
    			this.clear();
    
    			logger.logPad(`${teamA} are up to bat in inning ${index}`);
    			teamB.pitchTo(teamA);
    
    			this.clear();
    		}
    
    		listenToTeamActions() {
    			let [teamA, teamB] = this.teams;
    
    			teamA.addListener(this);
    			teamB.addListener(this);
    		}
    
    		trigger(event, ...args) {
    			// @todo make first letter of event uppercase
    			if (!!this[`on${event}`]) {
    				this[`on${event}`](...args);
    			}
    		}
    
    		onHit(player, type) {
    			const baseTypes = this.baseTypes;
    			const numberOfBases = type.bases % 4;
    
    			baseTypes[numberOfBases].bind(this)(player);
    		}
    	};
    })('diamond');
    
    const mets = new Team('Mets');
    const yankees = new Team('Yankees');
    const citiField = new Stadium(mets, yankees);
    console.log(mets, yankees);
    citiField.playGame(9);