1
0
Fork 0

New AI Duel page

This commit is contained in:
Michaël Lemaire 2017-02-24 01:34:31 +01:00
parent de8651440a
commit db194b4bf6
9 changed files with 233 additions and 98 deletions

4
.vscode/tasks.json vendored
View file

@ -11,14 +11,14 @@
"taskName": "build",
"isBuildCommand": true,
"isTestCommand": false,
"showOutput": "always",
"showOutput": "never",
"problemMatcher": "$tsc"
},
{
"taskName": "test",
"isBuildCommand": false,
"isTestCommand": true,
"showOutput": "always",
"showOutput": "silent",
"problemMatcher": "$tsc"
}
]

View file

@ -3,16 +3,93 @@
<head>
<meta charset="utf-8" />
<title>SpaceTac - AI Tournament</title>
<title>SpaceTac - AI Duel</title>
<style>
* {
margin: 0;
}
body {
background-color: #111;
color: #eee;
}
h1 {
margin-bottom: 38px;
font-size: 30px;
}
#duel {
width: 50vw;
margin-top: 30vh;
margin-left: auto;
margin-right: auto;
text-align: center;
}
button,
select {
font-size: 26px;
border: none;
border-radius: 6px;
background-color: #ddd;
cursor: pointer;
}
td {
padding: 12px;
width: 16vw;
text-align: center;
font-size: 26px;
}
td:nth-child(1) {
background-color: #611;
}
td:nth-child(3) {
background-color: #148;
}
td[colspan] {
background-color: inherit;
}
</style>
</head>
<body>
<script src="vendor/phaser/build/phaser.min.js"></script>
<script src="build.js"></script>
<div id="duel">
<h1>SpaceTac - AI Duel</h1>
<table>
<tr>
<td><select></select></td>
<td>versus</td>
<td><select></select></td>
</tr>
<tr>
<td colspan="3"><button>Start !</button></td>
</tr>
<tr>
<td>Win</td>
<td>Draw</td>
<td>Win</td>
</tr>
<tr>
<td class="win1"></td>
<td class="draw"></td>
<td class="win2"></td>
</tr>
</table>
</div>
<script>
window.onload = function () {
new TS.SpaceTac.AITournament();
TS.SpaceTac.AIDuel.setup(document.getElementById("duel"));
};
</script>
</body>

View file

@ -28,7 +28,7 @@
"karma-phantomjs-launcher": "~1.0",
"remap-istanbul": "~0.8",
"live-server": "~1.2",
"typescript": "~2.1",
"typescript": "~2.2",
"typings": "~1.4"
}
}

@ -1 +1 @@
Subproject commit d84417976adb78147656616fccd7cefc5eca21bb
Subproject commit ce9feb35874b0aa2686c80f1b7f56447044b9d74

133
src/core/ai/AIDuel.ts Normal file
View file

@ -0,0 +1,133 @@
module TS.SpaceTac {
/**
* Duel between two AIs, over multiple battles
*/
export class AIDuel {
static current: AIDuel | null = null
ai1: AbstractAI
ai2: AbstractAI
win1 = 0
win2 = 0
draw = 0
scheduled = null
stopped = false
onupdate: Function | null = null
constructor(ai1: AbstractAI, ai2: AbstractAI) {
this.ai1 = ai1;
this.ai2 = ai2;
}
/**
* Start the duel
*/
start(onupdate: Function | null = null) {
if (!this.scheduled) {
this.stopped = false;
this.scheduled = Timer.global.schedule(100, () => this.next());
this.onupdate = onupdate;
}
}
/**
* Stop the duel
*/
stop() {
this.stopped = true;
if (this.scheduled) {
Timer.global.cancel(this.scheduled);
this.scheduled = null;
}
}
/**
* Update the result of a single battle
*/
update(winner: AbstractAI | null) {
if (winner) {
if (winner == this.ai1) {
this.win1 += 1;
} else {
this.win2 += 1;
}
console.log(` => ${winner.name} wins`);
} else {
this.draw += 1;
console.log(" => draw");
}
if (this.onupdate) {
this.onupdate();
}
}
/**
* Perform the next battle
*/
next() {
console.log(`${this.ai1.name} vs ${this.ai2.name} ...`);
let battle = Battle.newQuickRandom();
let playing = battle.playing_ship;
while (!battle.ended && battle.turn < 100) {
//console.debug(`Turn ${battle.turn} - Ship ${battle.play_order.indexOf(playing)}`);
let ai = (playing.fleet == battle.fleets[0]) ? this.ai1 : this.ai2;
ai.timer = Timer.synchronous;
ai.ship = playing;
ai.play();
if (!battle.ended && battle.playing_ship == playing) {
console.error(`${ai.name} did not end its turn !`);
battle.advanceToNextShip();
}
playing = battle.playing_ship;
}
if (battle.ended && !battle.outcome.draw) {
this.update(battle.outcome.winner == battle.fleets[0] ? this.ai1 : this.ai2);
} else {
this.update(null);
}
if (!this.stopped) {
this.scheduled = Timer.global.schedule(100, () => this.next());
}
}
/**
* Setup the duel HTML page
*/
static setup(element: HTMLElement) {
let ais = [new BullyAI(null), new TacticalAI(null), new AbstractAI(null)];
ais.forEach((ai, idx) => {
let selects = element.getElementsByTagName("select");
for (let i = 0; i < selects.length; i++) {
let option = document.createElement("option");
option.setAttribute("value", idx.toString());
option.textContent = ai.name;
selects[i].appendChild(option);
}
});
let button = element.getElementsByTagName("button").item(0);
button.onclick = () => {
if (AIDuel.current) {
AIDuel.current.stop();
AIDuel.current = null;
button.textContent = "Start !";
} else {
let ai1 = parseInt(element.getElementsByTagName("select").item(0).value);
let ai2 = parseInt(element.getElementsByTagName("select").item(1).value);
AIDuel.current = new AIDuel(ais[ai1], ais[ai2]);
AIDuel.current.start(() => {
element.getElementsByClassName("win1").item(0).textContent = AIDuel.current.win1.toString();
element.getElementsByClassName("win2").item(0).textContent = AIDuel.current.win2.toString();
element.getElementsByClassName("draw").item(0).textContent = AIDuel.current.draw.toString();
});
button.textContent = "Stop !";
}
}
}
}
}

View file

@ -1,84 +0,0 @@
module TS.SpaceTac {
/**
* Tournament to test AIs against each other, over a lot of battles
*/
export class AITournament {
duels: [AbstractAI, number, AbstractAI, number][] = [];
constructor() {
this.addDuel(new AbstractAI(null), new BullyAI(null));
this.addDuel(new AbstractAI(null), new TacticalAI(null));
this.addDuel(new BullyAI(null), new TacticalAI(null));
this.start();
}
addDuel(ai1: AbstractAI, ai2: AbstractAI) {
ai1.timer = Timer.synchronous;
ai2.timer = Timer.synchronous;
this.duels.push([ai1, 0, ai2, 0]);
}
start(rounds = 100) {
if (this.duels.length == 0) {
console.error("No duel to perform");
return;
}
while (rounds--) {
this.duels.forEach(duel => {
console.log(`${duel[0].name} vs ${duel[2].name}`);
let winner = this.doOneBattle(duel[0], duel[2]);
if (winner) {
if (winner == duel[0]) {
duel[1] += 1;
} else {
duel[3] += 1;
}
console.log(` => ${winner.name} wins`);
} else {
console.log(" => draw");
}
});
}
console.log("--------------------------------------------------------");
console.log("Final result :");
this.duels.forEach(duel => {
let message = `${duel[0].name} ${duel[1]} - ${duel[2].name} ${duel[3]}`
console.log(message);
if (typeof document != "undefined") {
let line = document.createElement("div");
line.textContent = message;
document.body.appendChild(line);
}
});
}
doOneBattle(ai1: AbstractAI, ai2: AbstractAI): AbstractAI | null {
let battle = Battle.newQuickRandom();
let playing = battle.playing_ship;
while (!battle.ended && battle.turn < 100) {
//console.debug(`Turn ${battle.turn} - Ship ${battle.play_order.indexOf(playing)}`);
let ai = (playing.fleet == battle.fleets[0]) ? ai1 : ai2;
ai.ship = playing;
ai.play();
if (!battle.ended && battle.playing_ship == playing) {
console.error(`${ai.name} did not end its turn !`);
battle.advanceToNextShip();
}
playing = battle.playing_ship;
}
if (battle.ended && !battle.outcome.draw) {
return (battle.outcome.winner == battle.fleets[0]) ? ai1 : ai2;
} else {
return null;
}
}
}
}

View file

@ -30,17 +30,24 @@ module TS.SpaceTac {
this.timer = timer;
}
toString = () => this.name;
// Play a ship turn
// This will start asynchronous work. The AI will then call action methods, then advanceToNextShip to
// indicate it has finished.
play(): void {
this.workqueue = [];
this.started = (new Date()).getTime();
this.initWork();
if (this.workqueue.length > 0) {
this.processNextWorkItem();
if (!this.ship.playing) {
console.error(`${this.name} tries to play a ship out of turn`);
} else {
this.endTurn();
this.initWork();
if (this.workqueue.length > 0) {
this.processNextWorkItem();
} else {
this.endTurn();
}
}
}

View file

@ -14,7 +14,7 @@ module TS.SpaceTac.Specs {
}
// producer of FixedManeuver from a list of scores
let producer = (...scores: number[]) => iarray(scores.map(score => new FixedManeuver(score)));
let producer = (...scores: number[]) => imap(iarray(scores), score => new FixedManeuver(score));
let applied = [];
beforeEach(function () {
@ -27,11 +27,12 @@ module TS.SpaceTac.Specs {
ai.producers.push(producer(1, -8, 4));
ai.producers.push(producer(3, 7, 0, 6, 1));
ai.ship.playing = true;
ai.play();
expect(applied).toEqual([7]);
});
it("produces direct weapon hits", function () {
it("produces direct weapon shots", function () {
let battle = new Battle();
let ship0a = battle.fleets[0].addShip(new Ship(null, "0A"));
let ship0b = battle.fleets[0].addShip(new Ship(null, "0B"));

View file

@ -2,7 +2,7 @@
/// <reference path="Maneuver.ts"/>
module TS.SpaceTac {
type TacticalProducer = () => Maneuver | null;
type TacticalProducer = Iterator<Maneuver>;
type TacticalEvaluator = (Maneuver) => number;
/**
@ -47,8 +47,9 @@ module TS.SpaceTac {
}
// Produce a maneuver
let maneuver: Maneuver;
let producer = this.producers.shift();
let maneuver = producer();
[maneuver, producer] = producer();
if (maneuver) {
this.producers.push(producer);