Skip to content

Commit

Permalink
bumped version, added fairer challenges, more docs
Browse files Browse the repository at this point in the history
  • Loading branch information
dominikwilkowski committed May 6, 2024
1 parent a8c97ef commit ec68449
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 28 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "coup"
version = "1.0.0"
version = "1.1.0"
edition = "2021"
authors = ["Dominik Wilkowski <Hi@Dominik-Wilkowski.com>"]
license = "GPL-3.0-or-later"
Expand Down
93 changes: 84 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
# Coup

<p align="center"><img width="764" src="assets/coup.png"></p>

<p align="center">This is a CLI implementation of the game of <a href="http://gamegrumps.wikia.com/wiki/Coup">COUP</a>.</p>

<p align="center">
<a href="https://crates.io/crates/coup"><img src="https://img.shields.io/crates/v/coup.svg" alt="crates badge"></a>
<a href="https://docs.rs/coup"><img src="https://docs.rs/coup/badge.svg" alt="crates docs tests"></a>
<a href="https://github.com/dominikwilkowski/coup/actions/workflows/testing.yml"><img src="https://github.com/dominikwilkowski/coup/actions/workflows/testing.yml/badge.svg" alt="build status"></a>
</p>

This app is designed as a code challenge.
It challenges you to write a bot that plays [COUP](http://gamegrumps.wikia.com/wiki/Coup) against other bots.
# Coup

This is a coding game where bots you build play the popular card game
[COUP](https://en.wikipedia.org/wiki/Coup_(card_game)).

You write a bot to play COUP against other bots and tweak the behavior of your
bot until you find a winning strategy.

**_This works best if you buy the physical card game and play it a couple times maybe during your lunch break_**

The idea is to have three rounds of (1,000,000) games to find the winner (sum all scores).
Between each round you have time to make adjustments to your bot.
The idea is to have three rounds of (1,000,000) games to find the winner (sum
all scores). Between each round you have time to make adjustments to your bot.

## How does this work?

Expand All @@ -23,6 +26,7 @@ Between each round you have time to make adjustments to your bot.
- [How to run the game](#how-to-run-the-game)
- [How do I build a bot](#how-do-i-build-a-bot)
- [How does the engine work](#how-does-the-engine-work)
- [Changelog](#changelog)

## Rules

Expand Down Expand Up @@ -118,7 +122,8 @@ The default implementation are the methods of the `StaticBot` which only takes
`Income` and is forced to coup by the engine if it accumulated more or equal to
10 coins. It does not challenge, counter or counter challenge.

The simplest way to build a bot by falling back to static behavior for each method would be:
The simplest way to build a bot by falling back to static behavior for each
method would be:

```rust
use coup::bot::BotInterface;
Expand Down Expand Up @@ -166,6 +171,12 @@ Each function gets `context` passed in which will contain below infos:

## How does the engine work

The engine enforces all the rules laid out by the game as best as it can.
In areas where the rules are fuzzy or impossible to enforce in a computer world
it does best effort to come close to the real world game.

### The algorithm

```
match action
Assassination | Stealing
Expand All @@ -187,3 +198,67 @@ match action
- challenge round
- action
```

### Challenges

There are two different kind of challenges we distinguish for bots:
- A challenge to an action
- A challenge to a counter action

Because the rules of the game state challenges can be called by anyone (the
order is never articulated) and because the engine can't call all bots at the
same time, the engine will go one by one from the bot whos action just triggered
the challenge.

So If player A plays an action then the first bot asked if they want to
challenge this is player B. If player C does the action the first bot will be
player D.

### Targeting other bots

Because some actions require you to tell the engine who you'd like to inflict
this action upon, the engine expects you to give it a name. This name is derived
from each bots `get_name` method. The engine makes sure that there are never
multiple bots with the same name by adding a space and a number at the end of
duplicate names at the instantiation of the game.

So if we have this list of bots:
- Tici
- Bob
- Clara
- Bob

The engine will change the names to:
- Tici
- Bob
- Clara
- Bob 2

These names are then communicated to you via the [context](#the-context) struct.

### Auto couping

The rules state that if a player has 10 or more coins they have to coup the next
time it's their turn. The engine enforces this by calling the `on_auto_coup`
method instead of the `on_turn` method when it's the bots turn.

### Penalties

The engine will check what a bots plays is legal.
If a bot plays an action it can't due to insufficient funds (`couping` or
`assassination`) it will penalize this bot by taking a card from that bot (and
ask the bot which one by calling the `on_card_loss` method).

The same happened if a bot returns an action with an invalid target (a name of a
bot that does not exist).

## Changelog

### `v1.1.0`
Challenges and counter challenges are not more fair by making sure the first bot
being asked for a challenge is the bot next in line according to the bots
position. This way for every challenge-able action our counter action the first
bot is different and not, as before, the first bot in this game.

### `v1.0.0`
Initial release of new rust engine
70 changes: 53 additions & 17 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,19 @@ impl Coup {
};
}

fn get_bot_list_starting_from_name(&self, bot_name: &str) -> Vec<usize> {
let bot_index = self
.playing_bots
.iter()
.position(|bot_index| self.bots[*bot_index].name.clone() == bot_name)
.unwrap();
self.playing_bots[bot_index + 1..]
.iter()
.chain(self.playing_bots[..bot_index].iter())
.cloned()
.collect()
}

fn challenge_and_counter_round(
&mut self,
action: Action,
Expand Down Expand Up @@ -876,12 +889,10 @@ impl Coup {
// On Action::ForeignAid
// Does anyone want to counter this action?
let mut counterer_name = String::new();
for bot_index in self.playing_bots.iter() {
for bot_index in
self.get_bot_list_starting_from_name(&playing_bot_name).iter()
{
let bot = &self.bots[*bot_index];
// Skipping the challenger
if bot.name.clone() == playing_bot_name.clone() {
continue;
}

let countering = bot.interface.on_counter(
&Action::ForeignAid,
Expand Down Expand Up @@ -939,12 +950,8 @@ impl Coup {
action: &Action,
by: String,
) -> Option<String> {
for bot_index in self.playing_bots.iter() {
for bot_index in self.get_bot_list_starting_from_name(&by).iter() {
let bot = &self.bots[*bot_index];
// skipping the challenger
if bot.name.clone() == by.clone() {
continue;
}

let context = self.get_context(bot.name.clone());

Expand Down Expand Up @@ -2284,6 +2291,40 @@ mod tests {
);
}

#[test]
fn test_get_bot_list_starting_from_name() {
let mut coup = Coup::new(vec![
Box::new(StaticBot),
Box::new(StaticBot),
Box::new(StaticBot),
Box::new(StaticBot),
Box::new(StaticBot),
]);
coup.setup();
coup.playing_bots = vec![0, 1, 2, 3, 4];

assert_eq!(
coup.get_bot_list_starting_from_name("StaticBot"),
vec![1, 2, 3, 4]
);
assert_eq!(
coup.get_bot_list_starting_from_name("StaticBot 2"),
vec![2, 3, 4, 0]
);
assert_eq!(
coup.get_bot_list_starting_from_name("StaticBot 3"),
vec![3, 4, 0, 1]
);
assert_eq!(
coup.get_bot_list_starting_from_name("StaticBot 4"),
vec![4, 0, 1, 2]
);
assert_eq!(
coup.get_bot_list_starting_from_name("StaticBot 5"),
vec![0, 1, 2, 3]
);
}

#[test]
fn test_challenge_and_counter_round_assassination() {
struct ActionChallengeBot;
Expand Down Expand Up @@ -3458,10 +3499,7 @@ mod tests {
String::from("TestBot 2"),
);

assert_eq!(
coup.bots[0].interface.get_name(),
String::from("TestBoton_challenge_counter_round")
);
assert_eq!(coup.bots[0].interface.get_name(), String::from("TestBot"));
assert_eq!(coup.bots[1].interface.get_name(), String::from("TestBot"));
assert_eq!(
coup.bots[2].interface.get_name(),
Expand All @@ -3481,9 +3519,7 @@ mod tests {

assert_eq!(
coup.bots[0].interface.get_name(),
String::from(
"TestBoton_challenge_counter_round,on_challenge_counter_round"
)
String::from("TestBoton_challenge_counter_round")
);
assert_eq!(
coup.bots[1].interface.get_name(),
Expand Down

0 comments on commit ec68449

Please sign in to comment.