Unverified Commit 8fe390de authored by captnfab's avatar captnfab 🦃
Browse files

Merge branch 'dev'

parents a765bfa5 f0bac81f
......@@ -60,4 +60,4 @@ music/scottbuckley_titan.ogg:
--------------------------------------------------
Thanks to linkmauve <linkmauve_AT_linkmauve.fr> for helping us with the metainfo
file, and cleaning up the dist/net.tedomum.CapBattleship.svg file.
file.
......@@ -9,28 +9,46 @@
* Language: English, French, German, Italian, Bokmål
* Preferences saving in ```${XDG_CONFIG_HOME:-$HOME/.config}/capbattleship/conf```
## Download
* [v1.0~alpha5](https://forge.tedomum.net/captnfab/capbattleship/-/archive/v1.0alpha4/capbattleship-v1.0alpha5.tar.gz)
* [v1.0~alpha4](https://forge.tedomum.net/captnfab/capbattleship/-/archive/v1.0alpha4/capbattleship-v1.0alpha4.tar.gz)
* [v1.0~alpha3](https://forge.tedomum.net/captnfab/capbattleship/-/archive/v1.0alpha3/capbattleship-v1.0alpha3.tar.gz)
* [v1.0~alpha2](https://forge.tedomum.net/captnfab/capbattleship/-/archive/v1.0alpha2/capbattleship-v1.0alpha2.tar.gz)
* [v1.0~alpha1](https://forge.tedomum.net/captnfab/capbattleship/-/archive/v1.0alpha1/capbattleship-v1.0alpha1.tar.gz)
## Installation
* ArchLinux: [capbattleship-git](https://aur.archlinux.org/packages/capbattleship-git/) (AUR)
```shell
git clone https://aur.archlinux.org/capbattleship-git.git
cd capbattleship-git
makepkg -c -i -s
```
* Debian/Ubuntu/etc.: [capbattleship_last_all.deb](https://chezlefab.net/share/3f6c697fa65faf0baece/capbattleship_last_all.deb) (packaging officiel en cours)
```shell
wget 'https://chezlefab.net/share/3f6c697fa65faf0baece/capbattleship_last_all.deb'
apt install ./capbattleship_last_all.deb
```
* FlatPak:
```shell
flatpak install --user https://dl.flathub.org/build-repo/32143/net.tedomum.CapBattleship.flatpakref
flatpak run net.tedomum.CapBattleship
```
* pip:
```shell
wget 'https://forge.tedomum.net/captnfab/capbattleship/-/archive/master/capbattleship-master.tar.gz'
tar xf capbattleship-master.tar.gz
cd capbattleship-master
pip install .
```
* manual (linux) [tarball](https://forge.tedomum.net/captnfab/capbattleship/-/archive/master/capbattleship-master.tar.gz):
```shell
wget 'https://forge.tedomum.net/captnfab/capbattleship/-/archive/master/capbattleship-master.tar.gz'
tar xf capbattleship-master.tar.gz
capbattleship-master/capbattleship.py
* standalone (windows, unsupported) [zip](https://chezlefab.net/share/3f6c697fa65faf0baece/capbattleship_last.zip)
```
## Dependencies:
* python3 (works with >= 3.7.3) (uses os, configparser, random, enum, math, gettext, locale, glob)
* python3-pygame (>= 1.9.4) or python3-pygame-sdl2 (>= 7.3.5)
* python3-setuptools
* python3-importlib-metadata or python3-pkg-resources
* python3-setuptools (for install / package build only)
## Run:
* on extracted version:
./capbattleship.py
* on installed version:
capbattleship
License: see LICENSE file
......
......@@ -5,6 +5,6 @@ cap-battleship
A Pirate-themed BattleShip board-game
"""
__version__ = "0.0.5"
__version__ = "0.0.6"
__author__ = "Givors Fabien and Monteillard Damien"
from random import choice
def gen_coords(width, height):
return [ (x, y) for x in range(width) for y in range(height) ]
def filter_coords(coords, to_filter):
return [ c for c in coords if c not in to_filter ]
def keep_coords(coords, to_keep):
return [ c for c in coords if c in to_keep ]
def filter_diag(coords, f):
return [ (x,y) for (x,y) in coords if (y%f + x)%f != 0 ]
def all_coords(board):
return gen_coords(board.width, board.height)
def filter_hitmiss(coords, board):
return filter_coords(coords, board.hits+board.miss)
def filter_sunk(coords, board):
ships_coords = [ (x,y)
for ship in board.sunk
for x in range(max(ship.xmin-1, 0), min(ship.xmax+1, board.width))
for y in range(max(ship.ymin-1, 0), min(ship.ymax+1, board.height))
]
return filter_coords(coords, ships_coords)
def ship_found(player):
l = []
for ship in player.ships:
if ship.get_health() > 0 and ship.get_health() < ship.size:
for i in range(ship.size):
if ship.health[i] == 0:
l.append(ship.coords_of(i))
if len(l) == 0:
return None
l = sorted(l, key=lambda s: 10*s[0] + s[1])
xmin, ymin = xmax, ymax = l[0]
while (xmax + 1, ymax) in l:
xmax += 1
if xmin == xmax:
while (xmax, ymax + 1) in l:
ymax +=1
return (xmin, xmax, ymin, ymax)
def interesting_coords(player):
i = ship_found(player)
if i is None:
return []
else:
(xmin, xmax, ymin, ymax) = i
if xmin < xmax:
y = ymin
if xmin==0 or (xmin-1,y) in player.miss:
x = xmax+1
return [(x, y)]
elif xmax==player.width-1 or (xmax+1, y) in player.miss:
x = xmin-1
return [(x, y)]
else:
xs = [xmin-1, xmax+1]
return [(x, y) for x in xs]
elif ymin < ymax:
x = xmin
if ymin==0 or (x,ymin-1) in player.miss:
y = ymax+1
return [(x, y)]
elif ymax==player.width-1 or (x, ymax+1) in player.miss:
y = ymin-1
return [(x, y)]
else:
ys = [ymin-1, ymax+1]
return [(x, y) for y in ys]
else:
p = [(xmin-1, ymin), (xmin+1, ymin), (xmin, ymin-1), (xmin, ymin+1)]
l = []
for c in p:
if c not in player.miss and all([c[i]>=0 and c[i]<player.width for i in [0, 1]]):
l.append(c)
return l
def ai_cheat(player):
c = all_coords(player)
c = filter_hitmiss(c, player)
g = [ ship.coords_of(i)
for ship in player.ships
for i in range(ship.size)
if ship.health[i] > 0
]
c = filter_coords(c, g)
if len(c) > 0:
return choice(c)
else:
return choice(g)
def ai_easy(player):
c = all_coords(player)
c = filter_hitmiss(c, player)
return choice(c)
def ai_normal(player):
c = all_coords(player)
c = filter_hitmiss(c, player)
c = filter_sunk(c, player)
i = interesting_coords(player)
if len(i) > 0:
c = keep_coords(c, i)
return choice(c)
def ai_hard(player):
c = all_coords(player)
c = filter_hitmiss(c, player)
c = filter_sunk(c, player)
i = interesting_coords(player)
if len(i) > 0:
c = keep_coords(c, i)
else:
c = filter_diag(c, 2)
return choice(c)
def ai_insane(player):
c = [ ship.coords_of(i)
for ship in player.ships
for i in range(ship.size)
if ship.health[i] > 0
]
return choice(c)
from random import randrange, choice
from enum import Enum, auto
from .ai import ai_cheat, ai_easy, ai_normal, ai_hard, ai_insane
class Direction(Enum):
NORTH = auto()
SOUTH = auto()
......@@ -111,6 +113,7 @@ class PlayerBoard:
self.hits = []
self.miss = []
self.sunk = []
self.character = None
def add_ship(self, x, y, direction, size, name=""):
ship = Ship(x, y, direction, size, name)
......@@ -147,6 +150,7 @@ class Round:
OPTIONS = auto()
CREDITS = auto()
NEWGAME = auto()
CHARACTERS = auto()
PLAYER_SHIPS = auto()
CPU_SHIPS = auto()
PLAYER_TURN = auto()
......@@ -159,18 +163,40 @@ class Round:
self.stage=stage
self.options=options
def switch(self, state_type, options={}, notification=None):
if notification is None:
def switch(self, state_type, options={}, notification=None, save=False, restore=False):
old_stage = self.options.get('old_stage', None)
old_options = self.options.get('old_options', None)
if restore and old_stage is not None:
self.stage=old_stage
self.options.clear()
self.options=old_options
self.options['needs_redraw']=True
elif save:
old_stage = self.stage
old_options = self.options.copy()
self.stage=state_type
self.options.clear()
self.options=options.copy()
else:
self.options['old_stage'] = old_stage
self.options['old_options'] = old_options
elif notification is not None:
self.stage=self.Stage.NOTIFICATION
self.options.clear()
self.options=options.copy()
self.options['next_stage']=state_type
self.options['next_options']=options
self.options['message']=notification
self.options['old_stage'] = old_stage
self.options['old_options'] = old_options
else:
self.stage=state_type
self.options.clear()
self.options=options.copy()
self.options['old_stage'] = old_stage
self.options['old_options'] = old_options
def __init__(self, width, ui, tui=None):
if width > 26:
......@@ -181,9 +207,17 @@ class Round:
self.textui = tui
self.state = self.State(self.State.Stage.MENU)
self.show_credits = True
self.ships = [ ("The Anger", 2), ("The Victory Saber", 3), ("The Victory Saber", 3), ("The Pirate Storm", 4), ("The Princess", 5) ]
self.ships = [
("The Anger", 2),
("The Victory Saber", 3),
("The Victory Saber", 3),
("The Pirate Storm", 4),
("The Princess", 5),
]
self.ships.reverse()
self.player = None
self.cpu = None
self.characters = [ "Ethel Bonny", "Billy Wright", "Greaves Black Beard" ]
self.params = sync_config(None)
try:
......@@ -204,7 +238,14 @@ class Round:
def update(self):
if self.state.stage == self.State.Stage.QUIT:
self.quit = True
elif self.state.stage in [self.State.Stage.MENU, self.State.Stage.OPTIONS, self.State.Stage.CREDITS]:
elif self.state.stage == self.State.Stage.MENU:
next_stage = self.state.options.get('choice', None)
old_stage = self.state.options.get('old_stage', None)
if next_stage is self.State.Stage.NEWGAME and old_stage is not None:
self.state.switch(next_stage, restore=True)
elif next_stage is not None:
self.state.switch(next_stage)
elif self.state.stage in [self.State.Stage.OPTIONS, self.State.Stage.CREDITS]:
next_stage = self.state.options.get('choice', None)
if next_stage is not None:
self.state.switch(next_stage)
......@@ -212,7 +253,16 @@ class Round:
elif self.state.stage == self.State.Stage.NEWGAME:
self.cpu = PlayerBoard(self.width, self.width)
self.player = PlayerBoard(self.width, self.width)
self.state.switch(self.State.Stage.PLAYER_SHIPS)
self.state.switch(self.State.Stage.CHARACTERS)
elif self.state.stage == self.State.Stage.CHARACTERS:
if self.state.options.get('choice', None) is not None:
cc = self.state.options['choice']
if cc >= 0 and cc < len(self.characters):
self.player.character = cc
self.cpu.character = choice([ c for c in range(len(self.characters)) if c != self.player.character ])
self.state.switch(self.State.Stage.PLAYER_SHIPS)
else:
self.state.options['choice'] = None
elif self.state.stage == self.State.Stage.GAMEOVER:
if self.state.options.get('skip', False):
if self.show_credits == True:
......@@ -289,66 +339,16 @@ class Round:
except ValueError:
pass
elif self.state.stage == self.State.Stage.CPU_TURN:
def get_rand(player):
x,y = None,None
while (x is None and y is None) or (x,y) in player.miss or (x,y) in player.hits:
x = randrange(0, player.width)
y = randrange(0, player.height)
return (x,y)
def interesting_coords(player):
l = []
for ship in player.ships:
if ship.get_health() > 0 and ship.get_health() < ship.size:
for i in range(ship.size):
if ship.health[i] == 0:
l.append(ship.coords_of(i))
if len(l) == 0:
return None
l = sorted(l, key=lambda s: 10*s[0] + s[1])
xmin, ymin = xmax, ymax = l[0]
while (xmax + 1, ymax) in l:
xmax += 1
if xmin == xmax:
while (xmax, ymax + 1) in l:
ymax +=1
return (xmin, xmax, ymin, ymax)
if self.params['ai'] == AI.EASY or self.params['ai'] == AI.CHEAT:
x, y = get_rand(self.player)
elif self.params['ai'] == AI.NORMAL or self.params['ai'] == AI.HARD or self.params['ai'] == AI.INSANE:
i = interesting_coords(self.player)
if i is None:
x, y = get_rand(self.player)
else:
(xmin, xmax, ymin, ymax) = i
if xmin < xmax:
y = ymin
if xmin==0 or (xmin-1,y) in self.player.miss:
x = xmax+1
elif xmax==self.width-1 or (xmax+1, y) in self.player.miss:
x = xmin-1
else:
x = choice([xmin-1, xmax+1])
elif ymin < ymax:
x = xmin
if ymin==0 or (x,ymin-1) in self.player.miss:
y = ymax+1
elif ymax==self.width-1 or (x, ymax+1) in self.player.miss:
y = ymin-1
else:
y = choice([ymin-1, ymax+1])
else:
p = [(xmin-1, ymin), (xmin+1, ymin), (xmin, ymin-1), (xmin, ymin+1)]
l = []
for c in p:
if c not in self.player.miss and all([c[i]>=0 and c[i]<self.width for i in [0, 1]]):
l.append(c)
(x,y) = choice(l)
if self.params['ai'] == AI.CHEAT:
x,y = ai_cheat(self.player)
elif self.params['ai'] == AI.EASY:
x,y = ai_easy(self.player)
elif self.params['ai'] == AI.NORMAL:
x,y = ai_normal(self.player)
elif self.params['ai'] == AI.HARD:
x,y = ai_hard(self.player)
elif self.params['ai'] == AI.INSANE:
x,y = ai_insane(self.player)
outcome = self.player.shoot(x,y)
......
import glob
from glob import glob
try:
import pygame_sdl2 as pygame
#raise ImportError
except ImportError:
import pygame
......@@ -41,7 +42,7 @@ class Sprite(pygame.sprite.Sprite):
self.resize_width = resize_width
self.images = []
if img_glob is not None:
for frame in sorted(glob.glob(img_glob)):
for frame in sorted(glob(img_glob)):
self.images.append(self._load_image(frame))
if len(self.images) > 0:
......
This diff is collapsed.
......@@ -6,6 +6,8 @@ class DummyUI:
def read_events(self, rnd):
if rnd.state.stage == rnd.State.Stage.MENU:
rnd.state.options['choice'] = rnd.State.Stage.PLAYER_SHIPS
elif rnd.state.stage == rnd.State.Stage.CHARACTERS:
rnd.state.options['choice'] = 0
elif rnd.state.stage == rnd.State.Stage.GAMEOVER:
rnd.state.options['skip'] = True
elif rnd.state.stage == rnd.State.Stage.NOTIFICATION:
......@@ -57,6 +59,8 @@ class TextUI:
if rnd.state.stage in [rnd.State.Stage.MENU, rnd.State.Stage.CREDITS, rnd.State.Stage.OPTIONS, rnd.State.Stage.GAMEOVER, rnd.State.Stage.NOTIFICATION]:
_ = input()
rnd.state.options['skip'] = True
elif rnd.state.stage == rnd.State.Stage.CHARACTERS:
rnd.state.options['choice'] = input("> ")
elif rnd.state.stage == rnd.State.Stage.PLAYER_SHIPS:
if rnd.state.options.get('x', None) is None or rnd.state.options.get('y', None) is None:
pos = input("> ")
......
......@@ -34,7 +34,7 @@
</screenshots>
<releases>
<release version="1.0~alpha5" date="2020-11-23"/>
<release version="1.0~alpha6" date="2020-12-01"/>
</releases>
<content_rating type="oars-1.1"/>
......
dist/net.tedomum.CapBattleship.png

21.4 KB | W: | H:

dist/net.tedomum.CapBattleship.png

12.7 KB | W: | H:

dist/net.tedomum.CapBattleship.png
dist/net.tedomum.CapBattleship.png
dist/net.tedomum.CapBattleship.png
dist/net.tedomum.CapBattleship.png
  • 2-up
  • Swipe
  • Onion skin
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment