New AI Duel page
This commit is contained in:
parent
de8651440a
commit
db194b4bf6
4
.vscode/tasks.json
vendored
4
.vscode/tasks.json
vendored
|
@ -11,14 +11,14 @@
|
||||||
"taskName": "build",
|
"taskName": "build",
|
||||||
"isBuildCommand": true,
|
"isBuildCommand": true,
|
||||||
"isTestCommand": false,
|
"isTestCommand": false,
|
||||||
"showOutput": "always",
|
"showOutput": "never",
|
||||||
"problemMatcher": "$tsc"
|
"problemMatcher": "$tsc"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"taskName": "test",
|
"taskName": "test",
|
||||||
"isBuildCommand": false,
|
"isBuildCommand": false,
|
||||||
"isTestCommand": true,
|
"isTestCommand": true,
|
||||||
"showOutput": "always",
|
"showOutput": "silent",
|
||||||
"problemMatcher": "$tsc"
|
"problemMatcher": "$tsc"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
81
out/ai.html
81
out/ai.html
|
@ -3,16 +3,93 @@
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<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>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<script src="vendor/phaser/build/phaser.min.js"></script>
|
<script src="vendor/phaser/build/phaser.min.js"></script>
|
||||||
<script src="build.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>
|
<script>
|
||||||
window.onload = function () {
|
window.onload = function () {
|
||||||
new TS.SpaceTac.AITournament();
|
TS.SpaceTac.AIDuel.setup(document.getElementById("duel"));
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
"karma-phantomjs-launcher": "~1.0",
|
"karma-phantomjs-launcher": "~1.0",
|
||||||
"remap-istanbul": "~0.8",
|
"remap-istanbul": "~0.8",
|
||||||
"live-server": "~1.2",
|
"live-server": "~1.2",
|
||||||
"typescript": "~2.1",
|
"typescript": "~2.2",
|
||||||
"typings": "~1.4"
|
"typings": "~1.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1 +1 @@
|
||||||
Subproject commit d84417976adb78147656616fccd7cefc5eca21bb
|
Subproject commit ce9feb35874b0aa2686c80f1b7f56447044b9d74
|
133
src/core/ai/AIDuel.ts
Normal file
133
src/core/ai/AIDuel.ts
Normal 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 !";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -30,17 +30,24 @@ module TS.SpaceTac {
|
||||||
this.timer = timer;
|
this.timer = timer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toString = () => this.name;
|
||||||
|
|
||||||
// Play a ship turn
|
// Play a ship turn
|
||||||
// This will start asynchronous work. The AI will then call action methods, then advanceToNextShip to
|
// This will start asynchronous work. The AI will then call action methods, then advanceToNextShip to
|
||||||
// indicate it has finished.
|
// indicate it has finished.
|
||||||
play(): void {
|
play(): void {
|
||||||
this.workqueue = [];
|
this.workqueue = [];
|
||||||
this.started = (new Date()).getTime();
|
this.started = (new Date()).getTime();
|
||||||
this.initWork();
|
|
||||||
if (this.workqueue.length > 0) {
|
if (!this.ship.playing) {
|
||||||
this.processNextWorkItem();
|
console.error(`${this.name} tries to play a ship out of turn`);
|
||||||
} else {
|
} else {
|
||||||
this.endTurn();
|
this.initWork();
|
||||||
|
if (this.workqueue.length > 0) {
|
||||||
|
this.processNextWorkItem();
|
||||||
|
} else {
|
||||||
|
this.endTurn();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ module TS.SpaceTac.Specs {
|
||||||
}
|
}
|
||||||
|
|
||||||
// producer of FixedManeuver from a list of scores
|
// 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 = [];
|
let applied = [];
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
|
@ -27,11 +27,12 @@ module TS.SpaceTac.Specs {
|
||||||
ai.producers.push(producer(1, -8, 4));
|
ai.producers.push(producer(1, -8, 4));
|
||||||
ai.producers.push(producer(3, 7, 0, 6, 1));
|
ai.producers.push(producer(3, 7, 0, 6, 1));
|
||||||
|
|
||||||
|
ai.ship.playing = true;
|
||||||
ai.play();
|
ai.play();
|
||||||
expect(applied).toEqual([7]);
|
expect(applied).toEqual([7]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("produces direct weapon hits", function () {
|
it("produces direct weapon shots", function () {
|
||||||
let battle = new Battle();
|
let battle = new Battle();
|
||||||
let ship0a = battle.fleets[0].addShip(new Ship(null, "0A"));
|
let ship0a = battle.fleets[0].addShip(new Ship(null, "0A"));
|
||||||
let ship0b = battle.fleets[0].addShip(new Ship(null, "0B"));
|
let ship0b = battle.fleets[0].addShip(new Ship(null, "0B"));
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
/// <reference path="Maneuver.ts"/>
|
/// <reference path="Maneuver.ts"/>
|
||||||
module TS.SpaceTac {
|
module TS.SpaceTac {
|
||||||
|
|
||||||
type TacticalProducer = () => Maneuver | null;
|
type TacticalProducer = Iterator<Maneuver>;
|
||||||
type TacticalEvaluator = (Maneuver) => number;
|
type TacticalEvaluator = (Maneuver) => number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -47,8 +47,9 @@ module TS.SpaceTac {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Produce a maneuver
|
// Produce a maneuver
|
||||||
|
let maneuver: Maneuver;
|
||||||
let producer = this.producers.shift();
|
let producer = this.producers.shift();
|
||||||
let maneuver = producer();
|
[maneuver, producer] = producer();
|
||||||
|
|
||||||
if (maneuver) {
|
if (maneuver) {
|
||||||
this.producers.push(producer);
|
this.producers.push(producer);
|
||||||
|
|
Loading…
Reference in a new issue