From 651bc1d1d3321e49c9b354c95c3a8ea47d0e8e27 Mon Sep 17 00:00:00 2001 From: Mohammad Mahdi Rahimi Date: Mon, 30 Aug 2021 01:16:29 +0900 Subject: [PATCH] initial commit --- .gitignore | 1 + README.md | 0 pz_risk/__init__.py | 0 pz_risk/board.py | 45 +++++ pz_risk/demo.ipynb | 130 ++++++++++++++ pz_risk/maps/world.json | 387 ++++++++++++++++++++++++++++++++++++++++ pz_risk/risk_env.py | 200 +++++++++++++++++++++ setup.py | 0 8 files changed, 763 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 pz_risk/__init__.py create mode 100644 pz_risk/board.py create mode 100644 pz_risk/demo.ipynb create mode 100644 pz_risk/maps/world.json create mode 100644 pz_risk/risk_env.py create mode 100644 setup.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..78a5bf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*/__pycache__/** diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/pz_risk/__init__.py b/pz_risk/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pz_risk/board.py b/pz_risk/board.py new file mode 100644 index 0000000..a5dde8a --- /dev/null +++ b/pz_risk/board.py @@ -0,0 +1,45 @@ +import numpy as np +import networkx as nx +import matplotlib.pyplot as plt +import json +import random + +BOARDS = {} + + +class Board: + def __init__(self, graph: nx.Graph, pos=None): + self.g = graph + self.pos = pos + def reset(self, n_agent, n_unit_per_agent, n_cell_per_agent): + n_cells = self.g.number_of_nodes() + assert n_cell_per_agent * n_agent == n_cells + remaining_cells = [i for i in self.g.nodes()] + for i in range(n_agent): + cells = random.sample(remaining_cells, n_cell_per_agent) + remaining_cells = [c for c in remaining_cells if c not in cells] + nx.set_node_attributes(self.g, {c: 1 for c in cells}, 'units') + nx.set_node_attributes(self.g, {c: i for c in cells}, 'player') + t = n_unit_per_agent - n_cell_per_agent + left_unit = t + for j, cell in enumerate(cells): + if left_unit <= 0: + break + x = left_unit if j + 1 == len(cells) else random.randint(1, min(left_unit, t // 3)) + print(x) + left_unit -= x + nx.set_node_attributes(self.g, {cell: x+1}, 'units') + + +def register_map(name, filepath): + global BOARDS + f = open(filepath) + m = json.load(f) + g = nx.Graph() + g.add_nodes_from([(cell['id'], cell) for cell in m['cells']]) + g.add_edges_from([e for e in m['edges']]) + assert min([d[1] for d in g.degree()]) > 0 + BOARDS[name] = Board(g, m['pos']) + + +register_map('world', 'maps/world.json') diff --git a/pz_risk/demo.ipynb b/pz_risk/demo.ipynb new file mode 100644 index 0000000..9239ccf --- /dev/null +++ b/pz_risk/demo.ipynb @@ -0,0 +1,130 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "import random\n", + "\n", + "import risk_env\n", + "\n", + "env = risk_env.env()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "outputs": [], + "source": [ + "env.reset()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 7, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[(1, {'name': 'Alaska', 'group': 'North America', 'id': 1, 'gid': 1}), (2, {'name': 'NW Territory', 'group': 'North America', 'id': 2, 'gid': 1, 'units': 1, 'player': 5}), (3, {'name': 'Green Land', 'group': 'North America', 'id': 3, 'gid': 1, 'units': 1, 'player': 2}), (4, {'name': 'Alberta', 'group': 'North America', 'id': 4, 'gid': 1, 'units': 1, 'player': 5}), (5, {'name': 'Ontario', 'group': 'North America', 'id': 5, 'gid': 1, 'units': 1, 'player': 0}), (6, {'name': 'Quebec', 'group': 'North America', 'id': 6, 'gid': 1, 'units': 1, 'player': 4}), (7, {'name': 'Central America', 'group': 'North America', 'id': 7, 'gid': 1, 'units': 1, 'player': 1}), (8, {'name': 'W USA', 'group': 'North America', 'id': 8, 'gid': 1, 'units': 1, 'player': 3}), (9, {'name': 'E USA', 'group': 'North America', 'id': 9, 'gid': 1, 'units': 1, 'player': 1}), (10, {'name': 'Venezuela', 'group': 'South America', 'id': 10, 'gid': 2, 'units': 1, 'player': 5}), (11, {'name': 'Peru', 'group': 'South America', 'id': 11, 'gid': 2}), (12, {'name': 'Brazil', 'group': 'South America', 'id': 12, 'gid': 2, 'units': 12, 'player': 3}), (13, {'name': 'Argentina', 'group': 'South America', 'id': 13, 'gid': 2, 'units': 1, 'player': 4}), (14, {'name': 'Egypt', 'group': 'Africa', 'id': 14, 'gid': 3, 'units': 1, 'player': 1}), (15, {'name': 'N Africa', 'group': 'Africa', 'id': 15, 'gid': 3, 'units': 1, 'player': 2}), (16, {'name': 'E Africa', 'group': 'Africa', 'id': 16, 'gid': 3, 'units': 3, 'player': 0}), (17, {'name': 'Congo', 'group': 'Africa', 'id': 17, 'gid': 3, 'units': 1, 'player': 2}), (18, {'name': 'S Africa', 'group': 'Africa', 'id': 18, 'gid': 3, 'units': 1, 'player': 4}), (19, {'name': 'Madagascar', 'group': 'Africa', 'id': 19, 'gid': 3, 'units': 1, 'player': 2}), (20, {'name': 'Iceland', 'group': 'Europe', 'id': 20, 'gid': 4, 'units': 1, 'player': 5}), (21, {'name': 'Scandinavia', 'group': 'Europe', 'id': 21, 'gid': 4, 'units': 5, 'player': 5}), (22, {'name': 'N Europe', 'group': 'Europe', 'id': 22, 'gid': 4, 'units': 13, 'player': 2}), (23, {'name': 'W Europe', 'group': 'Europe', 'id': 23, 'gid': 4, 'units': 1, 'player': 1}), (24, {'name': 'S Europe', 'group': 'Europe', 'id': 24, 'gid': 4, 'units': 3, 'player': 3}), (25, {'name': 'Ukraine', 'group': 'Europe', 'id': 25, 'gid': 4, 'units': 1, 'player': 5}), (26, {'name': 'Britain', 'group': 'Europe', 'id': 26, 'gid': 4, 'units': 1, 'player': 3}), (27, {'name': 'Ural', 'group': 'Asia', 'id': 27, 'gid': 5, 'units': 3, 'player': 4}), (28, {'name': 'Middle East', 'group': 'Asia', 'id': 28, 'gid': 5, 'units': 1, 'player': 3}), (29, {'name': 'Afghanistan', 'group': 'Asia', 'id': 29, 'gid': 5, 'units': 1, 'player': 4}), (30, {'name': 'India', 'group': 'Asia', 'id': 30, 'gid': 5, 'units': 10, 'player': 1}), (31, {'name': 'Siam', 'group': 'Asia', 'id': 31, 'gid': 5}), (32, {'name': 'China', 'group': 'Asia', 'id': 32, 'gid': 5, 'units': 1, 'player': 2}), (33, {'name': 'Japan', 'group': 'Asia', 'id': 33, 'gid': 5, 'units': 4, 'player': 4}), (34, {'name': 'Mongolia', 'group': 'Asia', 'id': 34, 'gid': 5, 'units': 2, 'player': 0}), (35, {'name': 'Irkutsk', 'group': 'Asia', 'id': 35, 'gid': 5}), (36, {'name': 'Yakutsk', 'group': 'Asia', 'id': 36, 'gid': 5, 'units': 1, 'player': 5}), (37, {'name': 'Siberia', 'group': 'Asia', 'id': 37, 'gid': 5, 'units': 1, 'player': 3}), (38, {'name': 'Kamchatka', 'group': 'Asia', 'id': 38, 'gid': 5, 'units': 8, 'player': 1}), (39, {'name': 'W Australia', 'group': 'Australia', 'id': 39, 'gid': 6, 'units': 1, 'player': 4}), (40, {'name': 'E Australia', 'group': 'Australia', 'id': 40, 'gid': 6, 'units': 8, 'player': 5}), (41, {'name': 'Indonesia', 'group': 'Australia', 'id': 41, 'gid': 6, 'units': 1, 'player': 2}), (42, {'name': 'New Guinea', 'group': 'Australia', 'id': 42, 'gid': 6, 'units': 10, 'player': 0})]\n" + ] + }, + { + "ename": "KeyError", + "evalue": "'player'", + "output_type": "error", + "traceback": [ + "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", + "\u001B[0;31mKeyError\u001B[0m Traceback (most recent call last)", + "\u001B[0;32m/tmp/ipykernel_14222/1551777326.py\u001B[0m in \u001B[0;36m\u001B[0;34m\u001B[0m\n\u001B[0;32m----> 1\u001B[0;31m \u001B[0menv\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mrender\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m", + "\u001B[0;32m/app/anaconda3/envs/risk/lib/python3.8/site-packages/pettingzoo/utils/wrappers/order_enforcing.py\u001B[0m in \u001B[0;36mrender\u001B[0;34m(self, mode)\u001B[0m\n\u001B[1;32m 55\u001B[0m \u001B[0;32massert\u001B[0m \u001B[0mmode\u001B[0m \u001B[0;32min\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mmetadata\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0;34m\"render.modes\"\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 56\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0m_has_rendered\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0;32mTrue\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m---> 57\u001B[0;31m \u001B[0;32mreturn\u001B[0m \u001B[0msuper\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mrender\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mmode\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 58\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 59\u001B[0m \u001B[0;32mdef\u001B[0m \u001B[0mstep\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mself\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0maction\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/app/anaconda3/envs/risk/lib/python3.8/site-packages/pettingzoo/utils/wrappers/base.py\u001B[0m in \u001B[0;36mrender\u001B[0;34m(self, mode)\u001B[0m\n\u001B[1;32m 47\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 48\u001B[0m \u001B[0;32mdef\u001B[0m \u001B[0mrender\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mself\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mmode\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0;34m\"human\"\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m---> 49\u001B[0;31m \u001B[0;32mreturn\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0menv\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mrender\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mmode\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 50\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 51\u001B[0m \u001B[0;32mdef\u001B[0m \u001B[0mreset\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mself\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/app/anaconda3/envs/risk/lib/python3.8/site-packages/pettingzoo/utils/wrappers/capture_stdout.py\u001B[0m in \u001B[0;36mrender\u001B[0;34m(self, mode)\u001B[0m\n\u001B[1;32m 10\u001B[0m \u001B[0;32mdef\u001B[0m \u001B[0mrender\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mself\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mmode\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0;34m\"human\"\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 11\u001B[0m \u001B[0;32mif\u001B[0m \u001B[0mmode\u001B[0m \u001B[0;34m==\u001B[0m \u001B[0;34m\"human\"\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m---> 12\u001B[0;31m \u001B[0msuper\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mrender\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 13\u001B[0m \u001B[0;32melif\u001B[0m \u001B[0mmode\u001B[0m \u001B[0;34m==\u001B[0m \u001B[0;34m\"ansi\"\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 14\u001B[0m \u001B[0;32mwith\u001B[0m \u001B[0mcapture_stdout\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m \u001B[0;32mas\u001B[0m \u001B[0mstdout\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/app/anaconda3/envs/risk/lib/python3.8/site-packages/pettingzoo/utils/wrappers/base.py\u001B[0m in \u001B[0;36mrender\u001B[0;34m(self, mode)\u001B[0m\n\u001B[1;32m 47\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 48\u001B[0m \u001B[0;32mdef\u001B[0m \u001B[0mrender\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mself\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mmode\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0;34m\"human\"\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m---> 49\u001B[0;31m \u001B[0;32mreturn\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0menv\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mrender\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mmode\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 50\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 51\u001B[0m \u001B[0;32mdef\u001B[0m \u001B[0mreset\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mself\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/app/risk/pz-risk/pz_risk/risk_env.py\u001B[0m in \u001B[0;36mrender\u001B[0;34m(self, mode)\u001B[0m\n\u001B[1;32m 82\u001B[0m \u001B[0mprint\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mG\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mnodes\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mdata\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0;32mTrue\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 83\u001B[0m nx.draw_networkx_nodes(G, pos,\n\u001B[0;32m---> 84\u001B[0;31m \u001B[0mnodelist\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0mc\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0;36m0\u001B[0m\u001B[0;34m]\u001B[0m \u001B[0;32mfor\u001B[0m \u001B[0mc\u001B[0m \u001B[0;32min\u001B[0m \u001B[0mG\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mnodes\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mdata\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0;32mTrue\u001B[0m\u001B[0;34m)\u001B[0m \u001B[0;32mif\u001B[0m \u001B[0mc\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0;36m1\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0;34m'player'\u001B[0m\u001B[0;34m]\u001B[0m \u001B[0;34m==\u001B[0m \u001B[0magent\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m,\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 85\u001B[0m node_color=COLORS[agent], **options)\n\u001B[1;32m 86\u001B[0m \u001B[0;31m# nx.draw_networkx_nodes(G, pos, nodelist=[4, 5, 6, 7], node_color=\"tab:blue\", **options)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/app/risk/pz-risk/pz_risk/risk_env.py\u001B[0m in \u001B[0;36m\u001B[0;34m(.0)\u001B[0m\n\u001B[1;32m 82\u001B[0m \u001B[0mprint\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mG\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mnodes\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mdata\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0;32mTrue\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 83\u001B[0m nx.draw_networkx_nodes(G, pos,\n\u001B[0;32m---> 84\u001B[0;31m \u001B[0mnodelist\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0mc\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0;36m0\u001B[0m\u001B[0;34m]\u001B[0m \u001B[0;32mfor\u001B[0m \u001B[0mc\u001B[0m \u001B[0;32min\u001B[0m \u001B[0mG\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mnodes\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mdata\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0;32mTrue\u001B[0m\u001B[0;34m)\u001B[0m \u001B[0;32mif\u001B[0m \u001B[0mc\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0;36m1\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0;34m'player'\u001B[0m\u001B[0;34m]\u001B[0m \u001B[0;34m==\u001B[0m \u001B[0magent\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m,\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 85\u001B[0m node_color=COLORS[agent], **options)\n\u001B[1;32m 86\u001B[0m \u001B[0;31m# nx.draw_networkx_nodes(G, pos, nodelist=[4, 5, 6, 7], node_color=\"tab:blue\", **options)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;31mKeyError\u001B[0m: 'player'" + ] + } + ], + "source": [ + "env.render()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 8, + "outputs": [ + { + "data": { + "text/plain": "" + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "env." + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/pz_risk/maps/world.json b/pz_risk/maps/world.json new file mode 100644 index 0000000..475db66 --- /dev/null +++ b/pz_risk/maps/world.json @@ -0,0 +1,387 @@ +{ + "name": "world", + "num_of_group": 6, + "num_of_cell": 42, + "cells": [ + { + "name": "Alaska", + "group": "North America", + "id": 1, + "gid": 1 + }, + { + "name": "NW Territory", + "group": "North America", + "id": 2, + "gid": 1 + }, + { + "name": "Green Land", + "group": "North America", + "id": 3, + "gid": 1 + }, + { + "name": "Alberta", + "group": "North America", + "id": 4, + "gid": 1 + }, + { + "name": "Ontario", + "group": "North America", + "id": 5, + "gid": 1 + }, + { + "name": "Quebec", + "group": "North America", + "id": 6, + "gid": 1 + }, + { + "name": "Central America", + "group": "North America", + "id": 7, + "gid": 1 + }, + { + "name": "W USA", + "group": "North America", + "id": 8, + "gid": 1 + }, + { + "name": "E USA", + "group": "North America", + "id": 9, + "gid": 1 + }, + { + "name": "Venezuela", + "group": "South America", + "id": 10, + "gid": 2 + }, + { + "name": "Peru", + "group": "South America", + "id": 11, + "gid": 2 + }, + { + "name": "Brazil", + "group": "South America", + "id": 12, + "gid": 2 + }, + { + "name": "Argentina", + "group": "South America", + "id": 13, + "gid": 2 + }, + { + "name": "Egypt", + "group": "Africa", + "id": 14, + "gid": 3 + }, + { + "name": "N Africa", + "group": "Africa", + "id": 15, + "gid": 3 + }, + { + "name": "E Africa", + "group": "Africa", + "id": 16, + "gid": 3 + }, + { + "name": "Congo", + "group": "Africa", + "id": 17, + "gid": 3 + }, + { + "name": "S Africa", + "group": "Africa", + "id": 18, + "gid": 3 + }, + { + "name": "Madagascar", + "group": "Africa", + "id": 19, + "gid": 3 + }, + { + "name": "Iceland", + "group": "Europe", + "id": 20, + "gid": 4 + }, + { + "name": "Scandinavia", + "group": "Europe", + "id": 21, + "gid": 4 + }, + { + "name": "N Europe", + "group": "Europe", + "id": 22, + "gid": 4 + }, + { + "name": "W Europe", + "group": "Europe", + "id": 23, + "gid": 4 + }, + { + "name": "S Europe", + "group": "Europe", + "id": 24, + "gid": 4 + }, + { + "name": "Ukraine", + "group": "Europe", + "id": 25, + "gid": 4 + }, + { + "name": "Britain", + "group": "Europe", + "id": 26, + "gid": 4 + }, + { + "name": "Ural", + "group": "Asia", + "id": 27, + "gid": 5 + }, + { + "name": "Middle East", + "group": "Asia", + "id": 28, + "gid": 5 + }, + { + "name": "Afghanistan", + "group": "Asia", + "id": 29, + "gid": 5 + }, + { + "name": "India", + "group": "Asia", + "id": 30, + "gid": 5 + }, + { + "name": "Siam", + "group": "Asia", + "id": 31, + "gid": 5 + }, + { + "name": "China", + "group": "Asia", + "id": 32, + "gid": 5 + }, + { + "name": "Japan", + "group": "Asia", + "id": 33, + "gid": 5 + }, + { + "name": "Mongolia", + "group": "Asia", + "id": 34, + "gid": 5 + }, + { + "name": "Irkutsk", + "group": "Asia", + "id": 35, + "gid": 5 + }, + { + "name": "Yakutsk", + "group": "Asia", + "id": 36, + "gid": 5 + }, + { + "name": "Siberia", + "group": "Asia", + "id": 37, + "gid": 5 + }, + { + "name": "Kamchatka", + "group": "Asia", + "id": 38, + "gid": 5 + }, + { + "name": "W Australia", + "group": "Australia", + "id": 39, + "gid": 6 + }, + { + "name": "E Australia", + "group": "Australia", + "id": 40, + "gid": 6 + }, + { + "name": "Indonesia", + "group": "Australia", + "id": 41, + "gid": 6 + }, + { + "name": "New Guinea", + "group": "Australia", + "id": 42, + "gid": 6 + } + ], + "edges": [ + [1, 2], + [1, 4], + [1, 38], + [2, 4], + [2, 3], + [2, 5], + [3, 5], + [3, 6], + [3, 20], + [4, 5], + [4, 8], + [5, 6], + [5, 8], + [5, 9], + [6, 9], + [7, 8], + [7, 9], + [7, 10], + [8, 9], + [10, 11], + [10, 12], + [11, 12], + [11, 13], + [12, 13], + [12, 15], + [14, 15], + [14, 16], + [14, 24], + [14, 28], + [15, 16], + [15, 17], + [15, 23], + [15, 24], + [16, 17], + [16, 18], + [16, 19], + [17, 18], + [18, 19], + [20, 21], + [20, 26], + [21, 26], + [21, 25], + [21, 22], + [22, 26], + [22, 25], + [22, 23], + [22, 24], + [23, 26], + [23, 24], + [24, 25], + [24, 28], + [25, 27], + [25, 28], + [25, 29], + [27, 29], + [27, 32], + [27, 37], + [28, 29], + [28, 30], + [29, 30], + [29, 32], + [30, 31], + [30, 32], + [31, 32], + [31, 41], + [32, 34], + [32, 37], + [33, 34], + [33, 38], + [34, 38], + [34, 37], + [34, 35], + [35, 36], + [35, 37], + [35, 38], + [36, 37], + [36, 38], + [39, 40], + [39, 41], + [39, 42], + [40, 42], + [41, 42] + ], + "pos": [ + [-1, 1], + [-0.8, 1], + [-0.6, 1], + [-1, 0.8], + [-0.8, 0.8], + [-0.6, 0.8], + [-0.8, 0.4], + [-1, 0.6], + [-0.6, 0.6], + [-0.8, 0.2], + [-1, 0], + [-0.6, 0], + [-0.8, -0.6], + [0, 0], + [-0.4, 0], + [0, -0.2], + [-0.2, -0.4], + [-0.2, -0.6], + [0, -0.6], + [-0.4, 1], + [-0.2, 1], + [-0.2, 0.6], + [-0.4, 0.4], + [-0.2, 0.2], + [0, 0.8], + [-0.4, 0.8], + [0.2, 0.8], + [0, 0.2], + [0.2, 0.4], + [0.4, 0.2], + [0.6, 0.2], + [0.6, 0.4], + [0.8, 0.6], + [0.6, 0.6], + [0.6, 0.8], + [0.6, 1], + [0.4, 1], + [0.8, 1], + [0.6, -0.4], + [0.8, -0.4], + [0.6, 0], + [0.8, 0] + ] +} \ No newline at end of file diff --git a/pz_risk/risk_env.py b/pz_risk/risk_env.py new file mode 100644 index 0000000..ed05d38 --- /dev/null +++ b/pz_risk/risk_env.py @@ -0,0 +1,200 @@ +from gym.spaces import Discrete, MultiDiscrete, Tuple, Dict +from pettingzoo import AECEnv +from pettingzoo.utils import agent_selector +from pettingzoo.utils import wrappers + +import numpy as np +import networkx as nx +from matplotlib import pyplot as plt +from board import Board, BOARDS +NUM_ITERS = 100 +COLORS = [ + 'tab:red', + 'tab:blue', + 'tab:green', + 'tab:purple', + 'tab:pink', + 'tab:cyan', +] + +def env(): + ''' + The env function wraps the environment in 3 wrappers by default. These + wrappers contain logic that is common to many pettingzoo environments. + We recommend you use at least the OrderEnforcingWrapper on your own environment + to provide sane error messages. You can find full documentation for these methods + elsewhere in the developer documentation. + ''' + env = raw_env() + env = wrappers.CaptureStdoutWrapper(env) + # env = wrappers.AssertOutOfBoundsWrapper(env) + env = wrappers.OrderEnforcingWrapper(env) + return env + + +class raw_env(AECEnv): + ''' + The metadata holds environment constants. From gym, we inherit the "render.modes", + metadata which specifies which modes can be put into the render() method. + At least human mode should be supported. + The "name" metadata allows the environment to be pretty printed. + ''' + metadata = {'render.modes': ['human'], "name": "rps_v2"} + + def __init__(self, n_agent=6, map='world'): + ''' + - n_agent: Number of Agent + - board: ['test', 'world', 'world2'] + The init method takes in environment arguments and + should define the following attributes: + - possible_agents + - action_spaces + - observation_spaces + + These attributes should not be changed after initialization. + ''' + super().__init__() + self.board = BOARDS[map] + n_nodes = self.board.g.number_of_nodes() + n_edges = self.board.g.number_of_edges() + self.possible_agents = [r for r in range(n_agent)] + self.agent_name_mapping = dict(zip(self.possible_agents, list(range(len(self.possible_agents))))) + + # Gym spaces are defined and documented here: https://gym.openai.com/docs/#spaces + self.action_spaces = {agent: {'reinforce': MultiDiscrete([n_nodes, 100]), + 'attack': Discrete(n_edges), + 'fortify': MultiDiscrete([n_nodes, n_nodes, 100]) + } for agent in self.possible_agents} + self.observation_spaces = {agent: Tuple(tuple([MultiDiscrete([n_agent, 100]) for _ in range(n_nodes)])) + for agent in self.possible_agents} + self.board.reset(n_agent, 20, 7) + + def render(self, mode="human"): + ''' + Renders the environment. In human mode, it can print to terminal, open + up a graphical window, or open up some other display that a human can see and understand. + ''' + G = self.board.g + # pos = nx.spring_layout(G, seed=3113794652) # positions for all nodes + # pos = nx.kamada_kawai_layout(G) # positions for all nodes + pos = {i+1: p for i, p in enumerate(self.board.pos)} if self.board.pos is not None else nx.kamada_kawai_layout(G) + if mode == 'human': + options = {"edgecolors": "tab:gray", "node_size": 800, "alpha": 0.9} + for agent in self.agents: + nx.draw_networkx_nodes(G, pos, + nodelist=[c[0] for c in G.nodes(data=True) if c[1]['player'] == agent], + node_color=COLORS[agent], **options) + # nx.draw_networkx_nodes(G, pos, nodelist=[4, 5, 6, 7], node_color="tab:blue", **options) + + # edges + nx.draw_networkx_edges(G, pos, width=1.0, alpha=0.5) + + # some math labels + labels = {c[0]: c[1]['units'] for c in G.nodes(data=True)} + nx.draw_networkx_labels(G, pos, labels, font_size=22, font_color="black") + + plt.tight_layout() + plt.axis("off") + plt.show() + else: + print('Wait for it') + + def observe(self, agent): + ''' + Observe should return the observation of the specified agent. This function + should return a sane observation (though not necessarily the most up to date possible) + at any time after reset() is called. + ''' + # observation of one agent is the previous state of the other + return np.array(self.observations[agent]) + + def close(self): + ''' + Close should release any graphical displays, subprocesses, network connections + or any other environment data which should not be kept around after the + user is no longer using the environment. + ''' + pass + + def reset(self): + ''' + Reset needs to initialize the following attributes + - agents + - rewards + - _cumulative_rewards + - dones + - infos + - agent_selection + And must set up the environment so that render(), step(), and observe() + can be called without issues. + + Here it sets up the state dictionary which is used by step() and the observations dictionary which is used by step() and observe() + ''' + self.agents = self.possible_agents[:] + self.rewards = {agent: 0 for agent in self.agents} + self._cumulative_rewards = {agent: 0 for agent in self.agents} + self.dones = {agent: False for agent in self.agents} + self.infos = {agent: {} for agent in self.agents} + # self.state = {agent: NONE for agent in self.agents} + # self.observations = {agent: NONE for agent in self.agents} + self.num_moves = 0 + ''' + Our agent_selector utility allows easy cyclic stepping through the agents list. + ''' + self._agent_selector = agent_selector(self.agents) + self.agent_selection = self._agent_selector.next() + + def step(self, action): + ''' + step(action) takes in an action for the current agent (specified by + agent_selection) and needs to update + - rewards + - _cumulative_rewards (accumulating the rewards) + - dones + - infos + - agent_selection (to the next agent) + And any internal state used by observe() or render() + ''' + if self.dones[self.agent_selection]: + # handles stepping an agent which is already done + # accepts a None action for the one agent, and moves the agent_selection to + # the next done agent, or if there are no more done agents, to the next live agent + return self._was_done_step(action) + + agent = self.agent_selection + + # the agent which stepped last had its _cumulative_rewards accounted for + # (because it was returned by last()), so the _cumulative_rewards for this + # agent should start again at 0 + self._cumulative_rewards[agent] = 0 + + # stores action of current agent + self.state[self.agent_selection] = action + + # collect reward if it is the last agent to act + if self._agent_selector.is_last(): + # rewards for all agents are placed in the .rewards dictionary + self.rewards[self.agents[0]], self.rewards[self.agents[1]] = REWARD_MAP[(self.state[self.agents[0]], self.state[self.agents[1]])] + + self.num_moves += 1 + # The dones dictionary must be updated for all players. + self.dones = {agent: self.num_moves >= NUM_ITERS for agent in self.agents} + + # observe the current state + for i in self.agents: + self.observations[i] = self.state[self.agents[1 - self.agent_name_mapping[i]]] + else: + # necessary so that observe() returns a reasonable observation at all times. + self.state[self.agents[1 - self.agent_name_mapping[agent]]] = NONE + # no rewards are allocated until both players give an action + self._clear_rewards() + + # selects the next agent. + self.agent_selection = self._agent_selector.next() + # Adds .rewards to ._cumulative_rewards + self._accumulate_rewards() + +if __name__ == '__main__': + e = env() + e.reset() + e.render() \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..e69de29