260 lines
9.2 KiB
Python
Executable file
260 lines
9.2 KiB
Python
Executable file
#!/usr/bin/env python3
|
||
# encoding: utf-8
|
||
|
||
# Playing battleships with archery.
|
||
# You fire two arrows, one for the column index and one for the line index.
|
||
# The score on the target gives you the index.
|
||
# An arrow going out of the target discards the current pair.
|
||
|
||
# Hypothesis:
|
||
# – The arrow distribution on the target follow a normal law.
|
||
# – The drift on target is symetric in all direction. There is no covariance in the distribution.
|
||
|
||
# Statistics questions:
|
||
# – Given a player's precision, what are the cells with the minimal probability of hit?
|
||
# A: 10/10, you'd better place ships on the 10th line and column.
|
||
# – What if players have different levels?
|
||
|
||
# Game theory questions:
|
||
# – What if players have a limited number of arrows?
|
||
# – What are the optimal mixed strategy (for ship placement and aiming)?
|
||
|
||
|
||
import sys
|
||
import math
|
||
import numpy as np
|
||
import matplotlib.pyplot as plt
|
||
from mpl_toolkits.axes_grid1 import make_axes_locatable, axes_size
|
||
|
||
def x(p):
|
||
return p[0]
|
||
def y(p):
|
||
return p[1]
|
||
def dist(u,v):
|
||
return np.sqrt( (x(v)-x(u))*(x(v)-x(u)) + (y(v)-y(u))*(y(v)-y(u)) )
|
||
|
||
|
||
def fire(nb_arrows, targeted_score, dispersion_score, dim=2):
|
||
"""Return a numpy array of arrows coordinates the form: [[x_1, … , x_n],[y_1, …, y_n]]"""
|
||
assert( 1 <= targeted_score <= 10)
|
||
# Variance of 1 = almost all arrows in center.
|
||
mean = np.zeros(nb_arrows) + (10 - targeted_score) * d_radius / math.sqrt(2)
|
||
var = np.ones(nb_arrows) * 2*dispersion_score # FIXME var = f(disp,d_radius)
|
||
arrows=np.random.normal(mean, var, (dim,nb_arrows))
|
||
return arrows
|
||
|
||
|
||
def make_target(nb_circles, d_radius):
|
||
"""Return an array of circles radius ranges of he form: [(inf_10,sup_10), …, (inf_1,sup_1)]"""
|
||
target_dist = []
|
||
prev_r = float("inf")
|
||
for t in np.arange( nb_circles, 0, -1 ):
|
||
r = t*d_radius-d_radius/2
|
||
target_dist.append((prev_r,r))
|
||
prev_r = r
|
||
target_dist.append((r,0))
|
||
return target_dist
|
||
|
||
|
||
def play(p1_level, p2_level, nb_circles, nb_arrows, dim = 2):
|
||
center = np.zeros((dim,nb_arrows))
|
||
p1,p2 = 0,1
|
||
score = np.zeros((2,nb_arrows*nb_circles*nb_circles))
|
||
arrows = []
|
||
for i in range(nb_circles):
|
||
arrows.append([None for i in range(nb_circles)])
|
||
# The players target (i,j)
|
||
n = 0
|
||
for i in range(nb_circles):
|
||
for j in range(nb_circles):
|
||
arrows[i][j] = [ fire(nb_arrows, i+1, p1_level, dim), fire(nb_arrows, j+1, p2_level, dim) ]
|
||
dists = [ dist(arrows[i][j][p1],center), dist(arrows[i][j][p2],center) ]
|
||
|
||
# Compute the scores reached by each arrow.
|
||
for a in range(nb_arrows):
|
||
d1 = dists[p1][a]
|
||
d2 = dists[p2][a]
|
||
# If at one of the arrows is out, let a score of zero for both. # FIXME option to discard zeros
|
||
if d1 < targets[0][1] and d2 < targets[0][1]:
|
||
# Find the reached circle and mark score.
|
||
for k,(sup,inf) in enumerate(targets):
|
||
if inf <= d1 < sup:
|
||
score[p1][n] = k
|
||
if inf <= d2 < sup:
|
||
score[p2][n] = k
|
||
n += 1
|
||
|
||
return arrows, score
|
||
|
||
|
||
def plot_targets(ax, targets):
|
||
nb_circles = len(targets)-1
|
||
|
||
# Pastel target colors
|
||
# targets_colors = ["gold","lightcoral","lightblue","lightgrey","white",]
|
||
|
||
# Official targets color
|
||
targets_colors = ["yellow","red","blue","black","white",]
|
||
|
||
prev_r = 0
|
||
for t,(inf,sup) in enumerate(targets):
|
||
face = plt.Circle((0, 0), sup, color=targets_colors[(nb_circles-t-1)//2], zorder=1, linewidth=0.2)
|
||
# border = plt.Circle((0, 0), sup, color="grey", fill=False,zorder=2)
|
||
ax.add_artist(face)
|
||
# ax.add_artist(border)
|
||
ax.set_aspect("equal")
|
||
# last circle
|
||
last_border = plt.Circle((0, 0), targets[0][1], color="grey", fill=False, zorder=2)
|
||
ax.add_artist(last_border)
|
||
|
||
|
||
def format_grid(im, ax, nb_circles):
|
||
# Grid
|
||
minticks = [i-0.5 for i in range(nb_circles+1)]
|
||
ax.set_xticks(minticks, minor=True)
|
||
ax.set_yticks(minticks, minor=True)
|
||
ax.grid(which="minor")
|
||
|
||
# Labels
|
||
majticks = [i for i in range(nb_circles)]
|
||
labels = [i+1 for i in range(nb_circles)]
|
||
ax.set_xticks(majticks)
|
||
ax.set_yticks(majticks)
|
||
ax.set_xticklabels(labels)
|
||
ax.set_yticklabels(labels)
|
||
|
||
for tick in ax.xaxis.get_major_ticks():
|
||
# tick.label.set_fontsize(8)
|
||
tick.label.set_fontsize(5)
|
||
|
||
for tick in ax.yaxis.get_major_ticks():
|
||
# tick.label.set_fontsize(8)
|
||
tick.label.set_fontsize(5)
|
||
|
||
# FIXME add color bars
|
||
aspect = 20
|
||
pad_fraction = 0.5
|
||
divider = make_axes_locatable(ax)
|
||
width = axes_size.AxesY(ax, aspect=1./aspect)
|
||
pad = axes_size.Fraction(pad_fraction, width)
|
||
cax = divider.append_axes("right", size=width, pad=pad)
|
||
cbar = plt.colorbar(im, cax=cax)
|
||
cbar.ax.tick_params(labelsize=5)
|
||
# v = np.linspace(0, 0.03, 3, endpoint=True)
|
||
# cbar.ax.set_yticklabels(["{:1.2f}".format(i) for i in v])
|
||
|
||
# fig1.colorbar(im, ax=axarr[1,i])
|
||
|
||
|
||
if __name__ == "__main__":
|
||
|
||
if len(sys.argv) > 1:
|
||
nb_arrows = int(sys.argv[1])
|
||
else:
|
||
nb_arrows = 10000
|
||
|
||
d_radius = 10
|
||
dim = 2
|
||
nb_circles = 10
|
||
player_level = {"01-gold":1, "02-yellow":3,"04-red":5,"06-blue":9,"08-black":13,"10-white":17,"100-crap":25,"900-wtf":40}
|
||
nb_lvl = len(player_level)
|
||
print("levels=",sorted(player_level.keys()))
|
||
|
||
targets = make_target(nb_circles, d_radius)
|
||
print("targets=",targets)
|
||
|
||
# One column per player level, two subplots:
|
||
# oup, an example target, down the density of probability.
|
||
fig1, axarr = plt.subplots(nb_lvl+1,nb_lvl+1)
|
||
|
||
lvl_score = {}
|
||
k = 0
|
||
for i,pl1 in enumerate(sorted(player_level.keys())):
|
||
for j,pl2 in enumerate(sorted(player_level.keys())):
|
||
fi, fj = i+1, j+1
|
||
print(k,"/",nb_lvl*nb_lvl,":",pl1,"VS",pl2)
|
||
k+=1
|
||
sys.stdout.flush()
|
||
|
||
# FIRE ARROWS
|
||
arrows, score = play(player_level[pl1], player_level[pl2], nb_circles, nb_arrows, dim)
|
||
lvl_score[(pl1,pl2)] = score
|
||
|
||
# PLOT TARGET
|
||
plot_targets(axarr[0,fj], targets)
|
||
plot_targets(axarr[fi,0], targets)
|
||
|
||
# PLOT (some) ARROWS
|
||
if i==j:
|
||
aim_p1, aim_p2 = 10,10
|
||
max_points = 100
|
||
lim = 1.5 * (nb_circles * d_radius)
|
||
axarr[0,fj].set_xlim((-lim,lim))
|
||
axarr[fi,0].set_xlim((-lim,lim))
|
||
axarr[0,fj].set_ylim((-lim,lim))
|
||
axarr[fi,0].set_ylim((-lim,lim))
|
||
p1,p2 = 0,1
|
||
p1_arrows = arrows[aim_p1-1][aim_p2-1][p1][:,:max_points]
|
||
p2_arrows = arrows[aim_p1-1][aim_p2-1][p2][:,:max_points]
|
||
|
||
axarr[0,fj].scatter(* p1_arrows , edgecolor="green", color="green", alpha=0.9, marker=".", zorder=3)
|
||
axarr[fi,0].scatter(*(-1*p2_arrows), edgecolor="green", color="green", alpha=0.9, marker=".", zorder=3)
|
||
|
||
# axarr[fi,0].set_title(pl1.split("-")[1])
|
||
axarr[fi,0].text(-200, 0, pl1.split("-")[1], ha='center', va='center', rotation='vertical')
|
||
axarr[0,fj].set_title(pl2.split("-")[1])
|
||
|
||
axarr[0,fj].axes.get_yaxis().set_visible(False)
|
||
axarr[fi,0].axes.get_yaxis().set_visible(False)
|
||
axarr[0,fj].axes.get_xaxis().set_visible(False)
|
||
axarr[fi,0].axes.get_xaxis().set_visible(False)
|
||
|
||
|
||
# PLOT proba maps
|
||
|
||
# Compute probabilities of hit
|
||
H,xe,ye = np.histogram2d(*score, bins=11, normed=True)
|
||
|
||
# Plot the normalized histogram without out arrows.
|
||
if i==j:
|
||
colormap = "cubehelix"
|
||
else:
|
||
colormap = "viridis"
|
||
|
||
im = axarr[fi,fj].imshow(H[1:,1:], interpolation='nearest', origin='low', cmap=colormap,)
|
||
format_grid(im, axarr[fi,fj], nb_circles)
|
||
|
||
|
||
# PLOT RANDOM TEAMS
|
||
# Draw random player levels couples
|
||
nb_games = int(math.sqrt(nb_arrows)*10)
|
||
nb_subarrows = max(int(math.sqrt(nb_arrows)*10), nb_arrows)
|
||
pl1 = [int(i) for i in np.random.randn(nb_games) + len(player_level)//2]
|
||
pl2 = [int(i) for i in np.random.randn(nb_games) + len(player_level)//2]
|
||
for i in range(nb_games):
|
||
if 0 > pl1[i] > len(player_level):
|
||
pl1[i] = np.random.uniform(0,len(player_level))
|
||
if 0 > pl2[i] > len(player_level):
|
||
pl2[i] = np.random.uniform(0,len(player_level))
|
||
|
||
p1,p2 = 0,1
|
||
score1 = []
|
||
score2 = []
|
||
for i in range(nb_games):
|
||
print(i,"/",nb_games,end="\r")
|
||
k1, k2 = (pl1[i], pl2[i])
|
||
lvl_pair = list(player_level.keys())[k1], list(player_level.keys())[k2]
|
||
full_score = lvl_score[lvl_pair]
|
||
score1 = np.concatenate( (score1, np.random.choice(full_score[p1], (nb_subarrows))) )
|
||
score2 = np.concatenate( (score2, np.random.choice(full_score[p2], (nb_subarrows))) )
|
||
|
||
H,xe,ye = np.histogram2d(score1, score2, bins=11, normed=True)
|
||
im = axarr[0,0].imshow(H[1:,1:], interpolation='nearest', origin='low', cmap="inferno",)
|
||
format_grid(im, axarr[0,0], nb_circles)
|
||
|
||
|
||
# s = 1000
|
||
# h,w = s * 3, s * 4
|
||
# plt.savefig("target_eq-levels.png",dpi=300, figsize=(h,w))
|
||
|
||
plt.show()
|