Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Python application

on: [push]

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Set up Python 3.8
uses: actions/setup-python@v1
with:
python-version: 3.8
- name: Install pipenv
uses: dschep/install-pipenv-action@v1
- name: Install dependencies
run: |
pipenv install --dev
#- name: Lint with flake8
# run: |
# pip install flake8
# # stop the build if there are Python syntax errors or undefined names
# flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
# flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Test with pytest
run: |
pipenv run pytest
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -126,4 +126,4 @@ venv.bak/
dmypy.json

# Pyre type checker
.pyre/
.pyre/
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"python.pythonPath": "${workspaceFolder}/.venv/bin/python"
}
14 changes: 14 additions & 0 deletions Pipfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true

[dev-packages]
pytest = "*"

[packages]
opensimplex = "*"
colorama = "*"

[requires]
python_version = "3.8"
101 changes: 101 additions & 0 deletions Pipfile.lock

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

7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Running Locally

```
PIPENV_VENV_IN_PROJECT=1
pipenv install
```

17 changes: 13 additions & 4 deletions main_package/field.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,34 @@
logging.basicConfig(level=logging.INFO)

class FieldTypeEnum(Enum):
EMPTY = Fore.LIGHTBLACK_EX + "E"
BASE = Fore.RED + "B"
ANT = Fore.BLUE + "A"
FOOD = Fore.GREEN + "F"

GRASS = Fore.LIGHTBLACK_EX + "g"
FOREST = Fore.LIGHTBLACK_EX + "F"
WATER = Fore.LIGHTBLACK_EX + "w"
DEEP_WATER = Fore.LIGHTBLACK_EX + "W"
ROCK = Fore.LIGHTBLACK_EX + "R"
SAND = Fore.LIGHTBLACK_EX + "S"
DRY_GRASS = Fore.LIGHTBLACK_EX + "D"
TALL_GRASS = Fore.LIGHTBLACK_EX + "G"

class Field:
log = logging.getLogger(__name__)

def __init__(self, xpos:int, ypos:int):
def __init__(self, xpos:int, ypos:int, type: FieldTypeEnum):
self.xpos = xpos
self.ypos = ypos
self.type = FieldTypeEnum.EMPTY
self.emptyType = type
self.type = type
self.entity = None

def getPos(self) -> Tuple[int, int]:
return self.xpos, self.ypos

def resetToEmpty(self):
self.type = FieldTypeEnum.EMPTY
self.type = self.emptyType
self.entity = None

def setEntity(self, entity) -> bool:
Expand Down
19 changes: 11 additions & 8 deletions main_package/gameBoard.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import Tuple, List, Set
from main_package.fieldEntities.ant import Ant
from main_package.field import *
from main_package.mapGenerator import *
from main_package.fieldEntities.base import Base
from main_package.fieldEntities.food import Food
from main_package.interfaces.attackable import Attackable
Expand All @@ -12,15 +13,17 @@ class gameBoard:
log = logging.getLogger(__name__)
validForAttack = [FieldTypeEnum.ANT, FieldTypeEnum.BASE]

def __init__(self, xdim: int = 10, ydim: int = 10):
def __init__(self, xdim: int = 50, ydim: int = 40):
"""
Initializes an empty board of the given dimensions
:param xdim: x dimension (exclusive)
:param ydim: y dimension (exclusive)
"""
self.xdim = xdim
self.ydim = ydim
self.gameBoard = [[Field(xpos=x, ypos=y) for x in range(xdim)] for y in range(ydim)]

mapGen = MapGenerator(xdim, ydim)
self.gameBoard = mapGen.map
self.ants: dict[str, Ant] = {}
self.playerBases = {}
self.players = []
Expand Down Expand Up @@ -49,8 +52,8 @@ def createBase(self, xpos: int, ypos: int, player: str) -> bool:

# field where base is placed must be empty
field = self.getField(xpos, ypos)
if field.type != FieldTypeEnum.EMPTY:
logging.error("Base cannot be placed on field that is not empty. Field is {}".format(field.type))
if field.type != FieldTypeEnum.GRASS:
logging.error("Base cannot be placed on field that is not grass. Field is {}".format(field.type))
return False

if player in self.playerBases.keys():
Expand Down Expand Up @@ -130,8 +133,8 @@ def createAnt(self, xpos: int, ypos: int, antId: str, player: str) -> bool:
if not any(f.type == FieldTypeEnum.BASE for f in neighbouring_fields):
self.log.error("Invalid Placement, no adjacent base")
return False
elif placementDesitnation.type is not FieldTypeEnum.EMPTY:
self.log.error("Invalid Placement, field not empty")
elif placementDesitnation.type is not FieldTypeEnum.GRASS:
self.log.error("Invalid Placement, field not grass")
return False

# check if player owns base near which they want to place ant
Expand All @@ -156,7 +159,7 @@ def moveAnt(self, antId: str, xpos: int, ypos: int) -> bool:
ant = self.ants[antId]
# determine valid fields for movement
fields = self.getNeighbouringFields(ant.fieldPosition)
validFields = filter(lambda x: x.type == FieldTypeEnum.EMPTY, fields)
validFields = filter(lambda x: x.type != FieldTypeEnum.ROCK, fields)

# is movement valid ?
fieldToMoveTo: Field = None
Expand Down Expand Up @@ -231,7 +234,7 @@ def getBase(self, playerName: str) -> Base or None:

def createFood(self, xpos: int, ypos: int, magnitude: int) -> bool:
targetField = self.getField(xpos, ypos)
if targetField is None or targetField.type is not FieldTypeEnum.EMPTY:
if targetField is None or targetField.type is not FieldTypeEnum.GRASS:
self.log.error("Invalid target ({},{}) for placing food.".format(xpos, ypos))
return False
if magnitude <= 0 or magnitude != magnitude: # test for negative or nan
Expand Down
79 changes: 79 additions & 0 deletions main_package/mapGenerator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
from main_package.field import *
from opensimplex import OpenSimplex

class MapGenerator:
resolution = 5
mapScale = 30 # bigger => softer land feature
moistureScale = 70 # bigger => softer land feature

waterMaxElevation = 20
sandMaxElevation = 2

rockMinElevation = 60

grassMinMoisture = 45
tallGrassMinElevation = 55
tallGrassMinMoisture = 40

forestMinMoisture = 0
forestMaxMoisture = 0
forestMaxElevation = 0

def __init__(self, width: int, height: int):
"""
Initializes a map board of the given dimensions
:param xdim: width (exclusive)
:param ydim: height (exclusive)
"""
self.width = width
self.height = height
self.noiseGenerator = OpenSimplex()
self.map = self.createMap()

def getTerrain(self, elevation: int, moisture: int) -> FieldTypeEnum:
e = elevation * 100 # elevation [0, 100]
m = moisture * 100 # moisture [0, 100]

if (e < self.waterMaxElevation / 3):
return FieldTypeEnum.DEEP_WATER
if (e < self.waterMaxElevation):
return FieldTypeEnum.WATER
if (e < self.waterMaxElevation + self.sandMaxElevation):
return FieldTypeEnum.SAND

if (e > self.rockMinElevation):
return FieldTypeEnum.ROCK
if (e > self.rockMinElevation - self.sandMaxElevation):
return FieldTypeEnum.TALL_GRASS

if (m < self.grassMinMoisture):
return FieldTypeEnum.DRY_GRASS
if (e < self.forestMaxElevation and m > self.forestMinMoisture and m < self.forestMaxMoisture):
return FieldTypeEnum.FOREST
if (e > self.tallGrassMinElevation and m > self.tallGrassMinMoisture):
return FieldTypeEnum.TALL_GRASS

return FieldTypeEnum.GRASS

def createMap(self):
map = []

for x in range(self.width / self.resolution):
map[x] = []

for y in range(self.height / self.resolution):
elevationValue = self.getNoise(x, y, self.mapScale)
moistureValue = self.getNoise(x, y, self.moistureScale)

# Now use the noise values to determine the block type
terrainType = self.getTerrain(elevationValue, moistureValue)

map[x][y] = Field(xpos=x, ypos=y, type=terrainType)

return map

def getNoise(self, x: int, y: int, noiseScale: int):
return self.noiseGenerator.noise2D(
x = x / noiseScale,
y = y / noiseScale
) / 2 + 0.5