Last active
April 18, 2021 05:26
-
-
Save gxm11/d41882fb24c60ca3e56efd381e2ce48a to your computer and use it in GitHub Desktop.
battleship game with mist
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# ship battle game | |
# 1. ships can generate a detect value to every block nearby, v = 1 / (dx * dx + dy * dy) | |
# 2. the sea has its own detect limit, which will decrease slightly | |
# 3. attack will add a temp detect value to 3x3 blocks | |
# 4. any block where detect value * 2 + temp value > sum(detect value) + detect limit will be visible | |
import numpy as np | |
import re | |
import string | |
import sys | |
class Ship_Battle: | |
def __init__(self, width, height): | |
self.width = width | |
self.height = height | |
def kernel_r2(dx, dy): | |
return 1 / (1 + dx * dx + dy * dy) | |
self.kernel = self.kernel_cache(kernel_r2) | |
def kernel_3x3(dx, dy): | |
if dx == 0 and dy == 0: | |
return 8 | |
if abs(dx) <= 1 and abs(dy) <= 1: | |
return 1 | |
return 0 | |
self.attack = {"normal": self.kernel_cache(kernel_3x3)} | |
def kernel_cache(self, kernel): | |
ret = np.zeros((self.width * 2, self.height * 2)) | |
for i in range(self.width * 2): | |
for j in range(self.height * 2): | |
ret[i, j] = kernel(i - self.width, j - self.height) | |
return ret | |
def reset(self): | |
self.turn = 0 | |
self.water_level = 1 | |
self.temp_value = np.zeros((self.width, self.height, 2)) | |
self.ships = self.create_ships(10) | |
self.broken_ships = [] | |
def winner(self): | |
if len(self.ships[0]) == 0: | |
if len(self.ships[1]) == 0: | |
return 2 | |
else: | |
return 1 | |
elif len(self.ships[1]) == 0: | |
return 0 | |
else: | |
return None | |
def create_ships(self, n): | |
# p0's ship is in 0 ~ w-1 h/2 ~ h-1 | |
pos = np.arange(self.width * self.height / 2, dtype=int) | |
np.random.shuffle(pos) | |
ships_0 = [] | |
for i in pos[0:n]: | |
_x, _y = i % self.width, i // self.width + self.height // 2 | |
ships_0.append((_x, _y)) | |
# p1's ship is in 0 ~ w-1 0 ~ h/2-1 | |
pos = np.arange(self.width * self.height / 2, dtype=int) | |
np.random.shuffle(pos) | |
ships_1 = [] | |
for i in pos[0:n]: | |
_x, _y = i % self.width, i // self.width | |
ships_1.append((_x, _y)) | |
return (ships_0, ships_1) | |
def update(self): | |
for i, ships in enumerate(self.ships): | |
value = self.value_int(1 - i) | |
for _x, _y in ships: | |
if value[_x, _y] == 1: | |
ships.remove((_x, _y)) | |
self.broken_ships.append((_x, _y)) | |
self.turn += 1 | |
self.water_level = self.water_level * 0.9 | |
self.temp_value = self.temp_value * 0.9 | |
def action(self, a): | |
pid, x, y, kind = a | |
x0, y0 = self.width - x, self.height - y | |
dv = self.attack[kind][x0:x0 + self.width, y0:y0 + self.height] | |
self.temp_value[:, :, pid] += dv | |
def calculate_value(self): | |
values = np.zeros((self.width, self.height, 2)) | |
for i, ships in enumerate(self.ships): | |
v = np.zeros((self.width, self.height)) | |
for _x, _y in ships: | |
x0, y0 = self.width - _x, self.height - _y | |
v += self.kernel[x0:x0 + self.width, y0:y0 + self.height] | |
values[:, :, i] = v | |
return values | |
def value_int(self, pid): | |
ret = np.zeros((self.width, self.height), dtype=int) | |
value = self.calculate_value() | |
v0 = value[:, :, pid] * 2 + self.temp_value[:, :, pid] | |
v1 = np.sum(value, axis=2) + self.water_level | |
ret[v0 >= v1] = 1 | |
return ret | |
def render(self, pid=0): | |
template = "T%-2d" | |
for i in range(self.width): | |
template += "%2s" % string.ascii_uppercase[i] | |
for j in range(self.height): | |
template += "\n" | |
template += "%3d" % (j + 1) | |
for i in range(self.width): | |
template += "%2s" | |
vars = [self.turn] | |
value = self.value_int(pid) | |
for j in range(self.width): | |
for i in range(self.height): | |
if (i, j) in self.broken_ships: | |
c = "#" | |
elif (i, j) in self.ships[pid]: | |
c = "x" | |
else: | |
if value[i, j] == 1: | |
if (i, j) in self.ships[1 - pid]: | |
c = "!" | |
else: | |
c = " " | |
else: | |
c = "." | |
vars.append(c) | |
print(template % tuple(vars)) | |
def render_action(self, a, pid=0): | |
value = self.value_int(pid) | |
if value[a[1], a[2]] == 1: | |
t = (a[0], string.ascii_uppercase[a[1]], a[2] + 1) | |
print("P%d attack %s%d" % t) | |
else: | |
print("P%d attack ?" % a[0]) | |
class Game: | |
def __init__(self, game): | |
self.game = game | |
def run(self): | |
g = self.game | |
g.reset() | |
g.render(0) | |
self.cpu = None | |
while g.winner() is None: | |
a0 = None | |
while a0 is None: | |
a0 = self.human_action(g) | |
a1 = self.cpu_action(g) | |
g.action(a0) | |
g.action(a1) | |
g.update() | |
g.render_action(a0) | |
g.render_action(a1) | |
g.render(0) | |
if g.winner() == 2: | |
print("draw") | |
else: | |
print("winner is P%d" % g.winner()) | |
def run_test(self): | |
g = self.game | |
w = [0, 0, 0, 0] | |
for i in range(1000): | |
g.reset() | |
self.cpu = None | |
while g.winner() is None: | |
a = self.human_action_test(g) | |
a = self.cpu_action(g) | |
g.action(a) | |
g.action(a) | |
g.update() | |
w[g.winner()] += 1 | |
w[3] += g.turn | |
w[3] = w[3] / (w[0] + w[1] + w[2]) | |
print("P0 win %d, P1 win %d, Draw %d, Mean Turns = %.2f" % tuple(w)) | |
def human_action(self, game): | |
str = input("[T%d] Type pos to attack: " % game.turn) | |
m = re.match(r"^(\w)(\d+)$", str) | |
if m is None: | |
return | |
if m.group(1) in string.ascii_uppercase: | |
x = ord(str[0]) - ord('A') | |
else: | |
x = ord(str[0]) - ord('a') | |
y = int(m.group(2)) - 1 | |
return (0, x, y, "normal") | |
def human_action_test(self, game): | |
_x = np.random.randint(game.width) | |
_y = np.random.randint(game.height // 2) | |
return (0, _x, _y, "normal") | |
def cpu_action(self, game): | |
if self.cpu is None: | |
pos = [] | |
for i in range(game.width * game.height // 2): | |
_x, _y = i % game.width, i // game.width + game.height // 2 | |
pos.append((_x, _y)) | |
self.cpu = [pos, None] | |
pos, last = self.cpu | |
value = game.value_int(1) | |
for i, j in pos: | |
if value[i, j] == 1: | |
pos.remove((i, j)) | |
if len(pos) > 0: | |
ix = np.random.randint(len(pos)) | |
action_x, action_y = pos[ix] | |
else: | |
_x = np.random.randint(game.width) | |
_y = np.random.randint(game.height) | |
return (1, _x, _y, "normal") | |
if last is not None: | |
x, y = last | |
x_shift, y_shift = 0, 0 | |
for i in [-1, 0, 1]: | |
for j in [-1, 0, 1]: | |
if 0 <= x + i < game.width and 0 <= y + j < game.height: | |
if value[x + i, y + j] == 0: | |
x_shift += i | |
y_shift += j | |
x_shift = int((x_shift + 0.5) // 1.5) | |
y_shift = int((y_shift + 0.5) // 1.5) | |
if abs(x_shift * y_shift) == 4: | |
x_shift, y_shift = x_shift / 2, y_shift / 2 | |
_x = x + x_shift | |
_y = y + y_shift | |
if (_x, _y) in pos: | |
action_x, action_y = _x, _y | |
self.cpu[0].remove((action_x, action_y)) | |
self.cpu[1] = (action_x, action_y) | |
return (1, action_x, action_y, "normal") | |
if __name__ == "__main__": | |
if len(sys.argv) == 2: | |
if sys.argv[1] == "test": | |
g = Game(Ship_Battle(12, 12)) | |
g.run_test() | |
exit() | |
g = Game(Ship_Battle(12, 12)) | |
while True: | |
g.run() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment