Giter VIP home page Giter VIP logo

ai-battle's Introduction

AI Battle

The purpose of this project is to practice coding, while having fun at the same time!

Used technologies

...that you'll touch:

  • javascript (this will be ~95% of what you'll touch)
  • node
  • npm

...that power the game

  • redux
  • react
  • create-react-app
    • webpack
    • babel

Setup

  1. Fork/clone repo
  2. Make sure you've installed node 6+
  3. Install dependencies: npm install
  4. Run: npm start
  5. Open browser at http://localhost:3000 (should happen automatically at step 4)

Optional:

When you want to get your AI merged, just send out a pull request and I'll merge it. This way we'll be able to battle each other's AI's!

Rules and objective

The game is a turn based game, a bit like the board game Risk. You can spawn units, move them around, and fight your opponent's units. Your objective is to capture all of the strategic capture points, or to completely eliminate your opponent.

Or more formally. You win as soon as you fulfill one of these conditions:

  • Capture all (4) capture points (the squares)
  • Eliminate all enemy units

Explanations

Empty Player 2 won
emptyboard winByCapture

In this Example the red player won by capturing the obejctives.

Key Functionality
triangle The triangle stands for a minor spawn point (spawns 1 unit at a time). From there you can create new units.
star The star stands for a major spawn point (spawns 2 units at a time). From there you can create new units.
square The square stands for an objective to cover.
simplePower This army has 1 unit.
morePower This army has 24 units.

Writing an AI

I'll walk you through the process of writing an AI for the game. This section covers the following steps:

  1. Setting up
  2. The AI skeleton
  3. Game's data structures

Setting up

  1. Create a javascript file in /src/players, this is where you name your AI. Let's say we name our AI the Vienna AI. In that case, create the file /src/players/viennaPlayer.js.
  2. Shamelessly copy/paste the contents of the Passive AI into your newly created file. It should look like this:
export default class PassivePlayer {
	static getName() { return 'Passive' };
	
	constructor(color) {
		this.name = PassivePlayer.getName();
		this.color = color;
	}
	
	play() {
		return [];
	}
}

Don't worry, we'll explain what all this does later.

  1. Now rename the obvious references to the Passive AI;
export default class ViennaPlayer {
	static getName() { return 'Vienna' };
	
	constructor(color) {
		this.name = ViennaPlayer.getName();
		this.color = color;
	}
	
	play() {
		return [];
	}
}
  1. Add your player to the game. Open /src/players/players.js, and... well, I'll let you figure this step out by yourself.
  2. If you've managed to properly add your AI to the game, it should now show up in the dropdowns in the game. Check it out by running npm start.

The AI skeleton

Those know a bit of modern javascript might already have recognized that a player is defined as a class. Every AI class should have the same API. Now the skeleton breaks up into two pieces:

  1. Boilerplate for the name and color
  2. The play() method

Name and color boilerplate

The following part of your AI basically defines your AI's name, and it stores the color it will be given when the game starts.

Don't think too much about it, this is just needed to make the game run.

	static getName() { return 'Vienna' };
	
	constructor(color) {
		this.name = ViennaPlayer.getName();
		this.color = color;
	}

The play() method

Now this is the core of your AI. The game will call the play() method on your AI when it's your turn to make a move. It is now up to you to respond to the game with a list of moves that you'll make this turn. To know what move you make, you'll need to know the current state of the board. The game passes this as a function parameter, so your play method actually looks like this:

play(board) {
	// ...insert your fancy AI code here
	return [
		{ unitCount: 2, from : { x: 4, y: 4 }, to: { x: 4, y: 3 } }, // Move 1
		{ unitCount: 6, from : { x: 2, y: 4 }, to: { x: 3, y: 4 } }, // Move 2
	];
}

In an imaginary human setting it would kinda look like this:

  • Game: "Hey ViennaPlayer, it's your turn."
  • Game: *Takes picture of board and passes it to ViennaPlayer*
  • ViennaPlayer: *Looks at picture, thinks for a while*
  • ViennaPlayer: "Okay, I move 2 units from (4, 4) to (4, 3), and 6 units from (2, 4) to (3, 4)"
  • Game: *Moves the units on the board, like ViennaPlayer requested*
  • Game: "Hey OpponentPlayer, it's your turn."
  • Game: *Takes picture of board and passes it to OpponentPlayer*
  • OpponentPlayer: *Looks at picture, thinks for a while*
  • OpponentPlayer: "Okay, I move 3 units from (0, 0) to (1, 0), and 1 unit from (3, 0) to (4, 0)"
  • Game: *Moves the units on the board, like OpponentPlayer requested*
  • ...repeat

The picture that Game passes in this setting is the board parameter that your AI receives. It's a snapshot of the state of the board at the time that your AI should make it's move. The moves that ViennaPlayer requests are the array of moves that you return at the end of your play method.

Game's data structures

Formally:

type Board = {
	tiles: Array<Array<Tile>>,
	players: [Player, Player],
	winner: boolean,
}

type Tile = {
	x: number,
	y: number,
	type: TileType,
	player: Player,
	unitCount: number,
}

type Move = {
	unitCount: number,
	from: {
		x: number,
		y: number,
	},
	to: {
		x: number,
		y: number,
	},
}

type Player = {
	// You shouldn't use player internals. Just check on `tile.player === this`
}

type TileType = 'NEUTRAL' | 'MINOR_SPAWN' | 'MAJOR_SPAWN' | 'CAPTURE_POINT'
// Access tile types like this:
import { tileTypes } from '../constants';
tileTypes.NEUTRAL // 'NEUTRAL'

// And the proper signature of your AI's play method:
play(board: Board): Array<Move>

Board

The top level object that you'll receive in your AI. This object contains all of the information that you'll need to decide which moves to make.

Tile

The board is divided into tiles. Every tile has a type, and can be occupied by a player's units, or not be occupied at all. The tiles are laid out over two dimensions; x and y. The dimensions are represented as a two-dimensional array. If this makes your head spin, don't worry too much about. All you need to know is how to retrieve a specific tile.

Let's say you want to retrieve the tile at position (x: 3, y: 1), or (3, 1) for short, this is what you'll need:

const someTile = board.tiles[3][1];

// Now you can inspect the tile
const tileIsOccupiedByMe = someTile.player === this;

// And decide what you want to do with it
if (tileIsOccupiedByMe === false) {
	const doIWantToAttackTheTile = someTile.unitCount < 10;
}

Utility functions

TODO

FAQ & troubleshooting

But I can cheat!?!?

Yes. Yes you can cheat. You could mutate the board state to trick the game into thinking you won. Actually, that's probably a nice way to learn something. The purpose of this game is to teach people coding, not to be a competitive gaming environment. If you wish to contribute to make the engine more robust; PR's are welcome.

The game crashed

The game will try to catch illegal moves as best it can, but it could happen that it crashes in some edge case. Please file an issue if you think the game crashed when it shouldn't have.

I think the rules/mechanics are bad/weird/suboptimal

This game was created over a weekend, and we aim to balance and improve it over time. Suggestions to rules and mechanics are welcome. Please file an issue.

The human interface sucks

We know. PR please :)

Contributing

As you might have read in the FAQ & troubleshooting section, we're open to suggestions and contributions. This game is all about teaching people to code. Contributing to the core game would be an awesome way to learn something.

Don't worry, we created this game for fun. We won't bite, and won't scrutinize you if the PR you submit doesn't live up to "elite coding standards".

We encourage people to brainstorm and collaborate on the game.

ai-battle's People

Contributors

createdd avatar edorivai avatar matthiasliszt avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

ai-battle's Issues

Playtest results

With the help of my amigo Vinsch (@VincentTheDutchDude), I playtested this game on a physical board to see how it holds up when played by two average humans.

It seems that the current codebase of the game does support different unit types, but that the actual game only uses one unit type. Naturally, we started with a test of the variant with only one unit type.

playtest

Such a single unit type variant proved to be rather one-dimensional. We didn't quite find the perfect strategy right away, but we do feel like such a strategy exists and anyone who plays the game for a while would find it. The single unit type variant is a bit like an adult tic-tac-toe in that regard.


The second variant included rock-paper-scissors unit types. We chose a variant where an army can only consist of units of the same type.

As mentioned in #1, such a restriction makes the game less cluttered, but also creates problems. What happens if an army of one type tries to move into the same tile as an allied army of another type? And what happens if an army of one type captures a spawn point of another type (which is very common)?

Our answers:

  • An army of one type moving into the same tile as an allied army of another type is illegal. Yes, this makes pathfinding more difficult (and we should probably include the difficult part outside of the AIs rather than implement it inside everyone of them).
  • The units in an army capturing a spawn point become of the same type as the spawn point. This means rocks turn into paper when they capture a paper spawn point. It means tank drivers step out of their tank and into an aeroplane when they capture an air spawn point.

We found that Lanchaster's law with it's current constants works quite well. Sometimes, adding one extra unit can completely turn a battle around. Which is nice. It adds depth.

Changing the type of units as they move onto a spawn point turns out to be pretty cool. In some situations you can take advantage of it: you can move an army onto a spawn point not to capture it, but rather to gain some extra units of that type. In other situations you have to move over a spawn point to get to the other side, and having the types changed is an interesting side-effect.


As a third variant, we capped the number of units that can spawn to 2. Every move is two phases: one in which you decide on which spawn points to spawn and one in which you decide which units to move where. (This would likely mean AI is defined in two functions instead of one.)

A spawn cap makes flanking much more realistic, we found. Normally, each player controls three spawn points which automatically guard themselves (as new units constantly spawn there). The spawn cap forces the player to leave one of those three spawn points a bit vulnerable. And as flanking becomes more realistic, watching the opponent's moves becomes more important.


In all three variants above, the player to move first is at an advantage. As a fourth variant, we tried an asymmetrical board in which one player moves first; but the other player starts with a spawn point strong against the other player's spawn point.

playtest

This variant has rock spawn points at 0, 0 and 3, 1; paper spawn points at 4, 4 and 0, 4; and scissors spawn points at 1, 3 and 4, 0. One player starts at 0, 0 (rock) and moves first; the other starts at 4, 4 (paper).

This last variant we tested seems to be quite balanced so far. From what we have learned, actually more balanced than the symmetrical one.

The asymmetrical board could make it more challenging to write AI. However, the spawn points are still located in a symmetrical way. The only thing that is asymmetrical is the typing.

playtest

Feature list v1

Engine:

  • Capture points + win condition
  • Unit types
  • Battle resolvement
  • Spawn phase
  • Capture time
  • Implement Lanchaster's law
  • Fog of war

Rules

  • Decide on rules, then add them as tasks here

AI utilities:

  • moveTowards(source: Tile, target: Tile): Tile; returns a tile you could move an army to from the source tile, to move towards the target tile. Basically naive path finding.
  • getClosestArmy(tile, player): Tile; returns the tile which is closest to the target tile, and occupied by one of your armies

UI:

  • Game ended view
  • Start/stop game buttons
  • AI picker
  • Game speed control
  • Full featured human player interface

AI's:

  • Passive (doesn't do anything)
  • Random
  • Turtle
  • Rush
  • Thief (tries to steal the win by rushing to capture points)
  • Swarm (prioritizes spawn points over capture points)
  • King of the hill (prioritizes controlling the major spawn point)
  • ...come up with more

Rule ideas

The rules of this game should make it easy to write average quality AI; but challenging to write top-notch AI.

Here are some ideas:

Rock, Paper, Scissors mechanics

Each unit is either a tank, a rocket launcher, or a rifle wielder. The rifle wielder is strong against rocket launchers; rocket launchers are strong against tanks; tanks are strong against rifle wielders.

Alternatives include: tank←aeroplane←anti-air(←tank); sword←lance←axe(←sword); light←anima←dark(←light); fire←water←grass(←fire); water←electric←ground(←water).

An army should probably consist of units of the same type. This does mean that moving units from a square to an adjacent square which already has a friendly army on it only works if the units and the army are of the same type. (You can move a tank into a friendly army of tanks; but not into a friendly army of rocket launchers.) This might make AI designing a tad more challenging.

You can choose which type of unit to spawn at a spawn point

A turn has two phases: the phase in which the player decides which units to spawn, and the phase in which players move units.

If an army can only consist of units of the same type, then the game should not ask a player which unit to spawn on a spawn point that already has an army on it. (If there are rifle wielders on the spawn point right now, an extra rifle wielder will be spawned.)

There are swamps, which tanks can not go through

If there is a utility function which does the pathfinding, this should not make writing AI more complicated. But it could introduce a tactical element. ("I can move here safely with my rifle wielders, because it's difficult for my opponent to send tanks here.")

A unit which has survived many battles becomes legendary

Maybe that makes the unit strong against its own type as well. Would this help the game in any way?

Tanks can fire to an adjacent field without moving there

There are towns

Battles which take place here favour rocket launchers and rifle wielders.

Moving should have more implications, since the game is all about moving.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.