Skip to content

Commit 4755a8b

Browse files
committed
Homeworlds move generation is active. Prepped for 0.2.1 release
1 parent 0ce5d3f commit 4755a8b

File tree

9 files changed

+473
-23
lines changed

9 files changed

+473
-23
lines changed

CHANGELOG.md

+8
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.2.1] - 2021-10-31
9+
10+
### Added
11+
12+
- Homeworlds now uses the expanded annotations feature of the renderer.
13+
- There is now a move generator for Homeworlds! It's not particularly efficient, but it appears to at least function.
14+
- A rudimentary AI has been added, but it's very uneven. The move tree for Homeworlds can balloon quickly with a lot of movement actions.
15+
816
## [0.2.0] - 2021-10-29
917

1018
### Added

package-lock.json

+17-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@abstractplay/gameslib",
3-
"version": "0.2.0",
3+
"version": "0.2.1",
44
"description": "TypeScript implementations of the core Abstract Play games, intended to be wrapped by the front- and backends",
55
"main": "build/index.js",
66
"types": "build/index.d.ts",
@@ -47,6 +47,7 @@
4747
"ajv": "^6.12.6",
4848
"graphology": "^0.21.0",
4949
"graphology-shortest-path": "^1.4.1",
50+
"js-combinatorics": "^1.5.4",
5051
"minmax-wt-alpha-beta-pruning": "^1.0.5",
5152
"rfdc": "^1.3.0"
5253
}

src/ais/blam.ts

+2-9
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ const gameRules = {
3535

3636
export class BlamAI extends AIBase {
3737
/**
38-
* The Blam AI prefers first of all having more players than anybody else.
38+
* The Blam AI prefers first of all having more pieces than anybody else.
3939
* After that, it values score, then number of captured pieces.
4040
*/
4141
public static evaluate(state: IBlamState): number {
@@ -51,15 +51,8 @@ export class BlamAI extends AIBase {
5151
stashcounts[k - 1] = v.reduce((a, b) => {return a + b;});
5252
});
5353

54-
// Calculate piece advantage compared with highest (or next highest) player
5554
const mystash = stashcounts[g.currplayer - 1];
56-
delete stashcounts[g.currplayer - 1];
57-
const maxstash = Math.max(...stashcounts);
58-
const diff = mystash - maxstash;
59-
if (Math.abs(diff) > 1) {
60-
score += (diff * wtPieces)
61-
}
62-
55+
score += mystash * wtPieces
6356
score += g.scores[g.currplayer - 1] * wtScore;
6457
score += g.caps[g.currplayer - 1] * wtCaps;
6558

src/ais/homeworlds.ts

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { HomeworldsGame, IHomeworldsState } from "../games";
2+
import {minmax} from 'minmax-wt-alpha-beta-pruning';
3+
import { AIBase } from "./_base";
4+
import { IAIResult } from ".";
5+
6+
const gameRules = {
7+
listMoves (state: IHomeworldsState): string[] {
8+
const g = new HomeworldsGame(state);
9+
return g.moves();
10+
},
11+
nextState (state: IHomeworldsState, move: string): IHomeworldsState {
12+
const g = new HomeworldsGame(state);
13+
g.move(move);
14+
return g.state();
15+
},
16+
terminalStateEval (state: IHomeworldsState): number|null {
17+
const g = new HomeworldsGame(state);
18+
// g.checkEOG();
19+
if (! g.gameover) {
20+
return null;
21+
}
22+
if (g.winner.includes(g.currplayer)) {
23+
return Infinity;
24+
} else {
25+
return -Infinity;
26+
}
27+
}
28+
}
29+
30+
export class HomeworldsAI extends AIBase {
31+
/**
32+
* The only thing the AI cares about is piece superiority.
33+
* Larger pieces are preferred over smaller ones.
34+
*/
35+
public static evaluate(state: IHomeworldsState): number {
36+
let score = 0;
37+
const wtLge = 2;
38+
const wtMed = 1.5;
39+
const wtSm = 1;
40+
41+
const g = new HomeworldsGame(state);
42+
// Find all ships you own and add their value to your score
43+
const myseat = g.player2seat();
44+
for (const sys of g.systems) {
45+
for (const ship of sys.ships) {
46+
if (ship.owner === myseat) {
47+
switch (ship.size) {
48+
case 1:
49+
score += ship.size * wtSm;
50+
break;
51+
case 2:
52+
score += ship.size * wtMed;
53+
break;
54+
case 3:
55+
score += ship.size * wtLge;
56+
break;
57+
default:
58+
throw new Error("Unrecognized ship size. This should never happen.");
59+
}
60+
}
61+
}
62+
}
63+
64+
return score;
65+
}
66+
67+
public static findmove(state: IHomeworldsState, plies: number): string {
68+
const result: IAIResult = minmax(state, gameRules, HomeworldsAI.evaluate, plies);
69+
if ( (result === undefined) || (! result.hasOwnProperty("bestMove")) || (result.bestMove === undefined) || (result.bestMove === null) ) {
70+
throw new Error("No best move found. This should never happen.");
71+
}
72+
return result.bestMove;
73+
}
74+
}

src/ais/index.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { AmazonsAI } from "./amazons";
33
import { BlamAI } from "./blam";
44
import { CannonAI } from "./cannon";
55
import { MchessAI } from "./mchess";
6+
import { HomeworldsAI } from "./homeworlds";
67

78
export interface IAIResult {
89
bestMove: string|null;
@@ -13,20 +14,22 @@ export interface IAI {
1314
findmove: (state: any, depth: number) => string;
1415
}
1516

16-
export { AIBase, AmazonsAI, BlamAI, CannonAI, MchessAI };
17+
export { AIBase, AmazonsAI, BlamAI, CannonAI, MchessAI, HomeworldsAI };
1718

1819
export const supportedGames: string[] = ["amazons"];
1920
export const fastGames: Map<string, number> = new Map([
2021
["amazons", 1],
2122
["blam", 3],
2223
["cannon", 3],
23-
["mchess", 5]
24+
["mchess", 5],
25+
["homeworlds", 4]
2426
]);
2527
export const slowGames: Map<string, number> = new Map([
2628
["amazons", 2],
2729
["blam", 5],
2830
["cannon", 5],
29-
["mchess", 7]
31+
["mchess", 7],
32+
["homeworlds", 6]
3033
]);
3134

3235
export function AIFactory(game: string): AIBase|undefined {
@@ -39,6 +42,8 @@ export function AIFactory(game: string): AIBase|undefined {
3942
return new CannonAI();
4043
case "mchess":
4144
return new MchessAI();
45+
case "homeworlds":
46+
return new HomeworldsAI();
4247
}
4348
return;
4449
}

0 commit comments

Comments
 (0)