Compare commits
No commits in common. 'alf' and 'brainstorm_fabi' have entirely different histories.
alf
...
brainstorm
@ -1 +0,0 @@
|
|||||||
.vscode/
|
|
@ -0,0 +1,33 @@
|
|||||||
|
# Fabi
|
||||||
|
## Spaceship progression game
|
||||||
|
* A player ...
|
||||||
|
* controls a single spaceship (in special cases multiple spaceships (2-4))
|
||||||
|
* travels to friendly planets/spacestations to buy/sell/trade resources
|
||||||
|
* travels to friendly planets/asteroids to gather resources
|
||||||
|
* upgrades his spaceship by buying parts/upgrades
|
||||||
|
* travels to enemy planets/spacestations/asteroids to capture them
|
||||||
|
* Players play together ...
|
||||||
|
* by combining perks/abilities/roles
|
||||||
|
* by sharing resources
|
||||||
|
* Players play against eachother ...
|
||||||
|
* by fighting them
|
||||||
|
* in order to steal resources/power over a planet/spacestation
|
||||||
|
|
||||||
|
* Planets/Spacestations/Asteroids/Spaceships travel along realistic orbits
|
||||||
|
* -> Dynamic Map Layout
|
||||||
|
* Requires strategic placement of player ships, but not in traditional 2d space but in orbits (now you're thinking in orbits)
|
||||||
|
* Fighting ...
|
||||||
|
* involves: spotting, targeting, damage dealing
|
||||||
|
* should take at least 1 min from spotting to damage dealing
|
||||||
|
* should not always result in the loss of a spaceship
|
||||||
|
* damaged spaceships should have the options to flee from the fight
|
||||||
|
* Progression involves ...
|
||||||
|
* upgrading your ship
|
||||||
|
* mining equipment / cargo capacity / weapons
|
||||||
|
* increasing your reputation by NPC factions
|
||||||
|
* by capturing stations/planets in their name
|
||||||
|
* by trading with them
|
||||||
|
|
||||||
|
* A match is won ...
|
||||||
|
* if all enemy players are destroyed
|
||||||
|
* or, reputation by all factions is above certain threshold
|
@ -1,99 +0,0 @@
|
|||||||
# Till
|
|
||||||
settings:
|
|
||||||
|
|
||||||
scifi/space - factions with small fleets and cities/outposts in space
|
|
||||||
|
|
||||||
scifi/shipcommander - focus on one ship (few additional ships possible)
|
|
||||||
- rpg, command of one bigger capital ship, with small frigates/cruisers as additional ships, setting a bit mad max style, post-apocalypse in space, damaged, rigged ships, nothing is build new, everything is salvaged or made from scrap
|
|
||||||
- different fighting styles: pure weaponry - long rage (sniping), short range (brawling), medium (balanced), crowd control (jamming, decoys), support (healing (but out of combat), refueling, rearming, recrewing), buffs/debuffing (increased sensor range, better sensors)
|
|
||||||
but fighting is more dependant on upgrades, and a few meaningful decisions (maneuvers, target selection) one does not aim or control the ship directly
|
|
||||||
in one fighting encounter parts of a ship can be damaged (for example the engine), after that the captain of the losing ship can retreat, the winner gets the destroyed part of the loser, when every part is destroyed one dies.
|
|
||||||
- map is a solar system, 2d, viewed from the solar north
|
|
||||||
- planets and ships move with orbital mechanics
|
|
||||||
- fog of war, a big focus is finding the other ships, getting to places where they were reveals possible flight paths
|
|
||||||
- upgrades at certain space stations (weapon space station, armor space station, propulsion space station), if enemies are at a space station they can attack you
|
|
||||||
- wide gameplay, more smaller ships with limit (5), tall gameplay one ship but good
|
|
||||||
- economy/progression: salvaging, exploration and fighting small NPCs with chance of finding gear (damaged) that is partly functional and that can be made 100% useful at the space stations
|
|
||||||
killing the npcs like in warcraft 3
|
|
||||||
- goal: destroying the other ship captains ship (lore: someone has payed you to do so), gametime is limited by the amount of scrap, every captain can lose his capital ship
|
|
||||||
|
|
||||||
scifi/ground based
|
|
||||||
- underground warfare (inspiration: lego mining sets; flash game motherload) based in tunnels, maybe wildwest style, small companies fighting over resources
|
|
||||||
- map is 2d, with up/down, left/right
|
|
||||||
- economy is mining/industry based with base building, upgrading resource buildings and defenses, mining outposts can be be build which allow more resources to be gathered, but transport vehicles can be attacked (mad max style feeling but underground with scifi)
|
|
||||||
- action/warfare small number of units with direct attacks (lasers, guns, sabotage), indirect attacks (collapsing tunnels, dropping stalactites, explosives/mines, flooding with water or lava)
|
|
||||||
- movement: tunnels fast movement, but tunnels need build time; digging is slower but allows free movement, obstacles in the world limit movement/digging of tunnels (hard rocks, lava lakes, underground lakes)
|
|
||||||
- goal: destruction of the enemy base
|
|
||||||
- downtime: digging tunnels, upgrading resources and vehicles
|
|
||||||
- wide gameplay: more outposts, maybe more but weaker vehicles | + more map control | + faster expansion | - easier to attack (every outpost is weaker, small number of vehicles are limiting) |
|
|
||||||
- tall gameplay: fewer harder to attack outposts and vehicles
|
|
||||||
|
|
||||||
|
|
||||||
strategy game
|
|
||||||
- gameplay like in risk/supremacy/axis&allies/aftertheday
|
|
||||||
- sectors can be captured and must be secured which takes time
|
|
||||||
- sectors grant resources and manpower
|
|
||||||
- possible settings: insect war, steampunk, scifi (robots/factorio)
|
|
||||||
- important feature: building infrastructure and destroying enemy infrastructure (roads, pipelines, canals, railways)
|
|
||||||
- connecting and holding certain sectors gives a boost/bonus to production of resources or units
|
|
||||||
- unit counts high (100-1000 units per army)
|
|
||||||
- goal: destroying enemy hq
|
|
||||||
- movement faster along infrastructure
|
|
||||||
- 2d map, but no grid
|
|
||||||
- warfare: rock/paper/scissor type units
|
|
||||||
- tall empire: better but fewer infrastructure, wide empire vice versa
|
|
||||||
- downtime: building infrastructure, unit movement
|
|
||||||
|
|
||||||
# Alf
|
|
||||||
## Setting: DND
|
|
||||||
|
|
||||||
Kleine Spieleranzahl (etwa 1-4). Reines PVE. Storylastig.
|
|
||||||
- Replayability durch randomization
|
|
||||||
|
|
||||||
## Setting: WC3 monster trainer
|
|
||||||
|
|
||||||
## Setting: Ballerburg
|
|
||||||
Playercount can be low to high. At the beginning only near range enemies. Striking distance increases with time/players eliminated.
|
|
||||||
Fits into the game time restriction.
|
|
||||||
|
|
||||||
Skill tree for replayability,
|
|
||||||
|
|
||||||
Make make it a tower defense with leaflet map api
|
|
||||||
|
|
||||||
## Setting: Legion TD
|
|
||||||
|
|
||||||
Round based tower defense.
|
|
||||||
|
|
||||||
# Fabi
|
|
||||||
## Spaceship progression game
|
|
||||||
* A player ...
|
|
||||||
* controls a single spaceship (in special cases multiple spaceships (2-4))
|
|
||||||
* travels to friendly planets/spacestations to buy/sell/trade resources
|
|
||||||
* travels to friendly planets/asteroids to gather resources
|
|
||||||
* upgrades his spaceship by buying parts/upgrades
|
|
||||||
* travels to enemy planets/spacestations/asteroids to capture them
|
|
||||||
* Players play together ...
|
|
||||||
* by combining perks/abilities/roles
|
|
||||||
* by sharing resources
|
|
||||||
* Players play against eachother ...
|
|
||||||
* by fighting them
|
|
||||||
* in order to steal resources/power over a planet/spacestation
|
|
||||||
|
|
||||||
* Planets/Spacestations/Asteroids/Spaceships travel along realistic orbits
|
|
||||||
* -> Dynamic Map Layout
|
|
||||||
* Requires strategic placement of player ships, but not in traditional 2d space but in orbits (now you're thinking in orbits)
|
|
||||||
* Fighting ...
|
|
||||||
* involves: spotting, targeting, damage dealing
|
|
||||||
* should take at least 1 min from spotting to damage dealing
|
|
||||||
* should not always result in the loss of a spaceship
|
|
||||||
* damaged spaceships should have the options to flee from the fight
|
|
||||||
* Progression involves ...
|
|
||||||
* upgrading your ship
|
|
||||||
* mining equipment / cargo capacity / weapons
|
|
||||||
* increasing your reputation by NPC factions
|
|
||||||
* by capturing stations/planets in their name
|
|
||||||
* by trading with them
|
|
||||||
|
|
||||||
* A match is won ...
|
|
||||||
* if all enemy players are destroyed
|
|
||||||
* or, reputation by all factions is above certain threshold
|
|
@ -1,2 +0,0 @@
|
|||||||
FLASK_APP=ebermergen
|
|
||||||
FLASK_ENV=development
|
|
@ -1,2 +0,0 @@
|
|||||||
venv
|
|
||||||
*.pyc
|
|
@ -1,12 +0,0 @@
|
|||||||
venv:
|
|
||||||
python3 -m venv venv
|
|
||||||
./venv/bin/pip install -U pip wheel setuptools
|
|
||||||
|
|
||||||
install:
|
|
||||||
./venv/bin/pip install -r requirements.txt
|
|
||||||
|
|
||||||
run:
|
|
||||||
./venv/bin/flask run --host 0.0.0.0
|
|
||||||
|
|
||||||
test:
|
|
||||||
./venv/bin/python -m unittest
|
|
@ -1,12 +0,0 @@
|
|||||||
# Backend for EbermergenTD
|
|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
Requirements: python3, python3-venv and pip
|
|
||||||
|
|
||||||
## Available scripts
|
|
||||||
|
|
||||||
- `make venv`: creates a python virtual environment
|
|
||||||
- `make install`: installs python dependencies
|
|
||||||
- `make run`: runs the development server
|
|
||||||
- `make test`: runs unittests
|
|
@ -1,53 +0,0 @@
|
|||||||
import json
|
|
||||||
from flask import Flask, jsonify
|
|
||||||
from flask_cors import CORS
|
|
||||||
|
|
||||||
from ebermergen.models.game import Game
|
|
||||||
from .lib import generate_map
|
|
||||||
|
|
||||||
from .lib.ebermergen_game import EbermergenGame
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
|
||||||
CORS(app)
|
|
||||||
|
|
||||||
games = {}
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/')
|
|
||||||
def index():
|
|
||||||
return 'Hello World'
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/generate')
|
|
||||||
def generate():
|
|
||||||
return jsonify(generate_map((10, 8)))
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/generate/<int:x>/<int:y>')
|
|
||||||
def generate_var(x, y):
|
|
||||||
return jsonify(generate_map((x, y)))
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/state')
|
|
||||||
def state():
|
|
||||||
a = Game()
|
|
||||||
a.seed_map()
|
|
||||||
return jsonify(a.serialize())
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/lala')
|
|
||||||
def lala():
|
|
||||||
return jsonify({k: v.state for (k, v) in games.items()})
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/new-game/<name>', methods=['GET', 'POST'])
|
|
||||||
def new_game(name):
|
|
||||||
games[name] = EbermergenGame()
|
|
||||||
games[name].start_game()
|
|
||||||
return jsonify({'message': 'success'})
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/action/<name>/<type>')
|
|
||||||
def action(name, type):
|
|
||||||
games[name].perform_action(type, {})
|
|
||||||
return jsonify({'message': 'success'})
|
|
@ -1,8 +0,0 @@
|
|||||||
import random
|
|
||||||
|
|
||||||
TERRAIN_TYPES = [0, 1, 2, 3]
|
|
||||||
|
|
||||||
|
|
||||||
def generate_map(dimensions):
|
|
||||||
x_len, y_len = dimensions
|
|
||||||
return [[random.choice(TERRAIN_TYPES) for y in range(y_len)] for x in range(x_len)]
|
|
@ -1,50 +0,0 @@
|
|||||||
import atexit
|
|
||||||
import time
|
|
||||||
import threading
|
|
||||||
import queue
|
|
||||||
|
|
||||||
from .gameloop import GameLoop
|
|
||||||
|
|
||||||
|
|
||||||
def tick(dt, first, second):
|
|
||||||
print('%.2f' % dt, first, second)
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def worker(state, action_queue):
|
|
||||||
state['counter'] = 0
|
|
||||||
state['actions'] = []
|
|
||||||
loop = GameLoop(tick=tick, fps=1, fixed_dt=True, args=(1, 2))
|
|
||||||
while True:
|
|
||||||
item = action_queue.get()
|
|
||||||
if item['action'] == 'STOP':
|
|
||||||
loop.stop()
|
|
||||||
if item['action'] == 'START':
|
|
||||||
loop.start()
|
|
||||||
state['counter'] += 1
|
|
||||||
state['actions'].append(item['action'])
|
|
||||||
|
|
||||||
|
|
||||||
class EbermergenGame:
|
|
||||||
"""Runs a single game instance
|
|
||||||
|
|
||||||
Holds the serialized state.
|
|
||||||
Provides methods to perform actions.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.state = {}
|
|
||||||
self.actions = queue.Queue()
|
|
||||||
self.thread = threading.Thread(
|
|
||||||
target=worker, args=(self.state, self.actions))
|
|
||||||
|
|
||||||
def start_game(self):
|
|
||||||
self.thread.start()
|
|
||||||
atexit.register(self.interupt)
|
|
||||||
|
|
||||||
def interupt(self):
|
|
||||||
# handle gracefull shutdown
|
|
||||||
pass
|
|
||||||
|
|
||||||
def perform_action(self, action, payload={}):
|
|
||||||
self.actions.put({'action': action, 'payload': payload})
|
|
@ -1,46 +0,0 @@
|
|||||||
import threading
|
|
||||||
import time
|
|
||||||
import random
|
|
||||||
|
|
||||||
|
|
||||||
class GameLoop():
|
|
||||||
"""Implements a loop that calls tick every 1/fps
|
|
||||||
|
|
||||||
tick gets called with the time delta to the last call as parameter
|
|
||||||
if fixed_dt is True the tick function gets called with 1/fps
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, fps=1, tick=None, fixed_dt=False, args=None):
|
|
||||||
self.fps = fps
|
|
||||||
self.tick = tick if tick is not None else self.default_tick
|
|
||||||
self.args = args or tuple()
|
|
||||||
self.fixed_dt = fixed_dt
|
|
||||||
self.running = False
|
|
||||||
self.last_frame_time = time.time()
|
|
||||||
self.thread = threading.Thread(target=self.loop)
|
|
||||||
|
|
||||||
def loop(self):
|
|
||||||
while self.running:
|
|
||||||
current_time = time.time()
|
|
||||||
dt = current_time - self.last_frame_time
|
|
||||||
self.last_frame_time = current_time
|
|
||||||
|
|
||||||
if self.fixed_dt:
|
|
||||||
self.tick(1 / self.fps, *self.args)
|
|
||||||
else:
|
|
||||||
self.tick(dt, *self.args)
|
|
||||||
|
|
||||||
sleep_time = (1 / self.fps) - (time.time() - self.last_frame_time)
|
|
||||||
if sleep_time > 0:
|
|
||||||
time.sleep(sleep_time)
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
self.running = True
|
|
||||||
self.thread.start()
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
self.running = False
|
|
||||||
|
|
||||||
def default_tick(self, dt):
|
|
||||||
print('ticked. Last tick was %.2f seconds ago' % dt)
|
|
||||||
time.sleep(random.choice([0.4, 0.5, 0.6, 1.4, 2.1]) * 1/self.fps)
|
|
@ -1,14 +0,0 @@
|
|||||||
class AutoID:
|
|
||||||
last_id = 0
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.increment_id()
|
|
||||||
self.id = self.last_id
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def increment_id(cls):
|
|
||||||
cls.last_id += 1
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def reset_id(cls):
|
|
||||||
cls.last_id = 0
|
|
@ -1,26 +0,0 @@
|
|||||||
|
|
||||||
from .auto_id import AutoID
|
|
||||||
|
|
||||||
|
|
||||||
class Town(AutoID):
|
|
||||||
def __init__(self, player, coords):
|
|
||||||
super().__init__()
|
|
||||||
self.player = player
|
|
||||||
self.coords = coords
|
|
||||||
self.level = 1
|
|
||||||
|
|
||||||
|
|
||||||
class Tower(AutoID):
|
|
||||||
def __init__(self, player, coords):
|
|
||||||
super().__init__()
|
|
||||||
self.player = player
|
|
||||||
self.coords = coords
|
|
||||||
self.level = 1
|
|
||||||
|
|
||||||
|
|
||||||
class Mob(AutoID):
|
|
||||||
def __init__(self, player, coords):
|
|
||||||
super().__init__()
|
|
||||||
self.player = player
|
|
||||||
self.coords = coords
|
|
||||||
self.level = 1
|
|
@ -1,48 +0,0 @@
|
|||||||
import random
|
|
||||||
import json
|
|
||||||
|
|
||||||
from .player import Player
|
|
||||||
from .map import Map
|
|
||||||
from .entities import Town
|
|
||||||
|
|
||||||
from ebermergen.lib.gameloop import GameLoop
|
|
||||||
|
|
||||||
|
|
||||||
class Game:
|
|
||||||
def __init__(self):
|
|
||||||
self.players = [Player('P1'), Player('P2')]
|
|
||||||
self.map = Map((20, 16))
|
|
||||||
self.towns = []
|
|
||||||
self.towers = []
|
|
||||||
self.mobs = []
|
|
||||||
|
|
||||||
def get_free_positions(self):
|
|
||||||
positions = self.map.get_positions()
|
|
||||||
positions = positions.difference([el.coords for el in self.towns])
|
|
||||||
positions = positions.difference([el.coords for el in self.towers])
|
|
||||||
return positions
|
|
||||||
|
|
||||||
def get_random_position(self):
|
|
||||||
return random.choice(tuple(self.get_free_positions()))
|
|
||||||
|
|
||||||
def seed_map(self):
|
|
||||||
self.towns.append(Town(self.players[0], self.get_random_position()))
|
|
||||||
self.towns.append(Town(self.players[1], self.get_random_position()))
|
|
||||||
self.towns.append(Town(self.players[0], self.get_random_position()))
|
|
||||||
self.towns.append(Town(self.players[1], self.get_random_position()))
|
|
||||||
|
|
||||||
def serialize(self):
|
|
||||||
naive = json.loads(json.dumps(self, default=lambda o: o.__dict__))
|
|
||||||
|
|
||||||
def reduce_player(entities):
|
|
||||||
for obj in entities:
|
|
||||||
obj['player'] = obj['player']['id']
|
|
||||||
|
|
||||||
def make_id_based(entities):
|
|
||||||
return {obj['id']: obj for obj in entities}
|
|
||||||
|
|
||||||
for entity_type in ['towns', 'mobs', 'towers']:
|
|
||||||
reduce_player(naive[entity_type])
|
|
||||||
naive[entity_type] = make_id_based(naive[entity_type])
|
|
||||||
naive['players'] = make_id_based(naive['players'])
|
|
||||||
return naive
|
|
@ -1,15 +0,0 @@
|
|||||||
|
|
||||||
from ebermergen.lib import generate_map
|
|
||||||
|
|
||||||
|
|
||||||
class Map:
|
|
||||||
def __init__(self, dimensions):
|
|
||||||
self.dimensions = dimensions
|
|
||||||
self.terrain = generate_map(dimensions)
|
|
||||||
|
|
||||||
def get_positions(self):
|
|
||||||
positions = set()
|
|
||||||
for x in range(self.dimensions[0]):
|
|
||||||
for y in range(self.dimensions[1]):
|
|
||||||
positions.add((x, y))
|
|
||||||
return positions
|
|
@ -1,7 +0,0 @@
|
|||||||
from .auto_id import AutoID
|
|
||||||
|
|
||||||
|
|
||||||
class Player(AutoID):
|
|
||||||
def __init__(self, name):
|
|
||||||
super().__init__()
|
|
||||||
self.name = name
|
|
@ -1,4 +0,0 @@
|
|||||||
flask==1.1.1
|
|
||||||
python-dotenv==0.10.3
|
|
||||||
autopep8==1.4.4
|
|
||||||
pylint==2.4.4
|
|
@ -1,35 +0,0 @@
|
|||||||
import unittest
|
|
||||||
|
|
||||||
from ebermergen.models.auto_id import AutoID
|
|
||||||
|
|
||||||
|
|
||||||
class TestAutoID(unittest.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
AutoID.reset_id()
|
|
||||||
|
|
||||||
def test_first_instance(self):
|
|
||||||
a = AutoID()
|
|
||||||
self.assertEqual(a.id, 1)
|
|
||||||
|
|
||||||
def test_increment(self):
|
|
||||||
a, b = AutoID(), AutoID()
|
|
||||||
self.assertEqual(a.id, 1)
|
|
||||||
self.assertEqual(b.id, 2)
|
|
||||||
|
|
||||||
def test_inheritance(self):
|
|
||||||
class ClassA(AutoID):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class ClassB(AutoID):
|
|
||||||
pass
|
|
||||||
|
|
||||||
a = ClassA()
|
|
||||||
b = ClassB()
|
|
||||||
self.assertEqual(a.id, 1)
|
|
||||||
self.assertEqual(b.id, 1)
|
|
||||||
a2 = ClassA()
|
|
||||||
b2 = ClassB()
|
|
||||||
self.assertEqual(a.id, 1)
|
|
||||||
self.assertEqual(b.id, 1)
|
|
||||||
self.assertEqual(a2.id, 2)
|
|
||||||
self.assertEqual(b2.id, 2)
|
|
@ -1,13 +0,0 @@
|
|||||||
import unittest
|
|
||||||
|
|
||||||
from ebermergen.models.game import Game
|
|
||||||
|
|
||||||
|
|
||||||
class TestSerializer(unittest.TestCase):
|
|
||||||
def test_instantiation(self):
|
|
||||||
a = Game()
|
|
||||||
self.assertEqual(2, len(a.players))
|
|
||||||
|
|
||||||
def test_seeding(self):
|
|
||||||
a = Game()
|
|
||||||
a.seed_map()
|
|
@ -1,10 +0,0 @@
|
|||||||
import unittest
|
|
||||||
|
|
||||||
from ebermergen.models.game import Game
|
|
||||||
|
|
||||||
|
|
||||||
class TestSerializer(unittest.TestCase):
|
|
||||||
def test_serialize(self):
|
|
||||||
a = Game()
|
|
||||||
a.seed_map()
|
|
||||||
a.serialize()
|
|
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": ["react-app", "prettier"]
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
node_modules/
|
|
||||||
build/
|
|
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"semi": false,
|
|
||||||
"singleQuote": true
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
# Hello there
|
|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
Requirements: node and npm
|
|
||||||
|
|
||||||
- `npm install`: installs dependencies in node_modules
|
|
||||||
|
|
||||||
## Available scripts
|
|
||||||
|
|
||||||
- `npm start`: starts a development server
|
|
||||||
- `npm run build`: make a production build
|
|
||||||
- `npm run lint`: lint js files using eslint
|
|
||||||
- `npm run format`: format js files using prettier
|
|
@ -1,8 +0,0 @@
|
|||||||
module.exports = api => {
|
|
||||||
// caching the babel config
|
|
||||||
api.cache.using(() => process.env.NODE_ENV)
|
|
||||||
return {
|
|
||||||
presets: ['@babel/preset-env', '@babel/preset-react']
|
|
||||||
// plugins: [api.env('development') && 'react-refresh/babel'].filter(Boolean)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,67 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "ebermergen",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"description": "",
|
|
||||||
"main": "index.js",
|
|
||||||
"scripts": {
|
|
||||||
"start": "webpack-dev-server --mode development",
|
|
||||||
"build": "webpack --mode production",
|
|
||||||
"build-fresh": "rm -rf build/ && npm run build",
|
|
||||||
"serve": "ws -d build --compress --hostname 0.0.0.0",
|
|
||||||
"test": "tests",
|
|
||||||
"lint": "eslint src",
|
|
||||||
"format": "prettier --write src/**/*.js"
|
|
||||||
},
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git@localhost:ebermergen"
|
|
||||||
},
|
|
||||||
"author": "",
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
|
||||||
"axios": "^0.19.0",
|
|
||||||
"classnames": "^2.2.6",
|
|
||||||
"debounce": "^1.2.0",
|
|
||||||
"react": "^16.12.0",
|
|
||||||
"react-dom": "^16.12.0",
|
|
||||||
"react-icons": "^3.8.0",
|
|
||||||
"react-redux": "^7.1.3",
|
|
||||||
"redux": "^4.0.4",
|
|
||||||
"regenerator-runtime": "^0.13.3",
|
|
||||||
"reselect": "^4.0.0",
|
|
||||||
"reselect-tools": "0.0.7"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@babel/core": "^7.7.5",
|
|
||||||
"@babel/preset-env": "^7.7.6",
|
|
||||||
"@babel/preset-react": "^7.7.4",
|
|
||||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.1.1",
|
|
||||||
"babel-eslint": "^10.0.3",
|
|
||||||
"babel-loader": "^8.0.6",
|
|
||||||
"css-loader": "^3.3.2",
|
|
||||||
"eslint": "^6.7.2",
|
|
||||||
"eslint-config-prettier": "^6.7.0",
|
|
||||||
"eslint-config-react-app": "^5.1.0",
|
|
||||||
"eslint-plugin-flowtype": "^4.5.2",
|
|
||||||
"eslint-plugin-import": "^2.19.1",
|
|
||||||
"eslint-plugin-jsx-a11y": "^6.2.3",
|
|
||||||
"eslint-plugin-react": "^7.17.0",
|
|
||||||
"eslint-plugin-react-hooks": "^2.3.0",
|
|
||||||
"html-webpack-plugin": "^4.0.0-beta.11",
|
|
||||||
"local-web-server": "^3.0.7",
|
|
||||||
"mini-css-extract-plugin": "^0.8.0",
|
|
||||||
"optimize-css-assets-webpack-plugin": "^5.0.3",
|
|
||||||
"postcss-flexbugs-fixes": "^4.1.0",
|
|
||||||
"postcss-loader": "^3.0.0",
|
|
||||||
"postcss-normalize": "^8.0.1",
|
|
||||||
"postcss-preset-env": "^6.7.0",
|
|
||||||
"prettier": "^1.19.1",
|
|
||||||
"react-refresh": "^0.7.0",
|
|
||||||
"style-loader": "^1.0.1",
|
|
||||||
"webpack": "^4.41.2",
|
|
||||||
"webpack-bundle-analyzer": "^3.6.0",
|
|
||||||
"webpack-cli": "^3.3.10",
|
|
||||||
"webpack-dev-server": "^3.9.0",
|
|
||||||
"webpackbar": "^4.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
plugins: [
|
|
||||||
require('postcss-flexbugs-fixes'),
|
|
||||||
require('postcss-preset-env'),
|
|
||||||
require('postcss-normalize')
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
body,
|
|
||||||
html {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
|
||||||
line-height: 1.5;
|
|
||||||
background-color: #fce6cb;
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
|
||||||
|
|
||||||
header {
|
|
||||||
border-bottom: 2px solid white;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0px 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
header h1 {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
|
||||||
border-top: 2px solid white;
|
|
||||||
margin-top: 1em;
|
|
||||||
padding: 0 10px;
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
|
|
||||||
import './App.css'
|
|
||||||
import { Canvas } from './components.js/Canvas'
|
|
||||||
|
|
||||||
export const App = () => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<header>
|
|
||||||
<h1>Ebermergen</h1>
|
|
||||||
</header>
|
|
||||||
<main>
|
|
||||||
<Canvas />
|
|
||||||
</main>
|
|
||||||
<footer>This is the footer</footer>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
import Axios from 'axios'
|
|
||||||
|
|
||||||
const BACKEND_HOST = 'http://169.254.61.222:5000'
|
|
||||||
|
|
||||||
export function getMap(x, y) {
|
|
||||||
return Axios.get(`${BACKEND_HOST}/generate/${x}/${y}`).then(data => data.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getState() {
|
|
||||||
return Axios.get(`${BACKEND_HOST}/state`).then(data => data.data)
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
.border {
|
|
||||||
border: 1px solid red;
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
import React, { useLayoutEffect, useRef } from 'react'
|
|
||||||
import {
|
|
||||||
main,
|
|
||||||
mousedown,
|
|
||||||
mousemove,
|
|
||||||
mouseup,
|
|
||||||
click,
|
|
||||||
dblclick,
|
|
||||||
mouseenter,
|
|
||||||
mouseleave
|
|
||||||
} from '../lib'
|
|
||||||
|
|
||||||
import styles from './Canvas.css'
|
|
||||||
|
|
||||||
export const Canvas = () => {
|
|
||||||
const canvasRef = useRef(null)
|
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
const canvas = canvasRef.current
|
|
||||||
main(canvas)
|
|
||||||
|
|
||||||
canvas.addEventListener('mousedown', mousedown)
|
|
||||||
canvas.addEventListener('mouseup', mouseup)
|
|
||||||
canvas.addEventListener('mousemove', mousemove)
|
|
||||||
canvas.addEventListener('click', click)
|
|
||||||
canvas.addEventListener('dblclick', dblclick)
|
|
||||||
canvas.addEventListener('mouseenter', mouseenter)
|
|
||||||
canvas.addEventListener('mouseout', mouseleave)
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<canvas
|
|
||||||
className={styles.border}
|
|
||||||
width={500}
|
|
||||||
height={400}
|
|
||||||
ref={canvasRef}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
import 'regenerator-runtime/runtime'
|
|
||||||
|
|
||||||
import React from 'react'
|
|
||||||
import ReactDOM from 'react-dom'
|
|
||||||
|
|
||||||
import { App } from './App'
|
|
||||||
import { getMap } from './api'
|
|
||||||
|
|
||||||
function createRootElement() {
|
|
||||||
const body = document.getElementsByTagName('body')[0]
|
|
||||||
const root = document.createElement('div')
|
|
||||||
root.setAttribute('id', 'root')
|
|
||||||
body.appendChild(root)
|
|
||||||
return root
|
|
||||||
}
|
|
||||||
ReactDOM.render(<App />, createRootElement())
|
|
||||||
|
|
||||||
getMap(10, 20).then(map => {
|
|
||||||
console.log(map)
|
|
||||||
})
|
|
@ -1,192 +0,0 @@
|
|||||||
console.log('import entrypoint lib')
|
|
||||||
|
|
||||||
let global_scale = 1
|
|
||||||
let global_offset = [0, 0]
|
|
||||||
|
|
||||||
const cos_60 = Math.cos((60 * Math.PI) / 180)
|
|
||||||
const sin_60 = Math.sin((60 * Math.PI) / 180)
|
|
||||||
|
|
||||||
const TileType = {
|
|
||||||
LAND: 0,
|
|
||||||
WATER: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
class TileGrid {
|
|
||||||
constructor(width = 100, height = 100) {
|
|
||||||
this.width = 100
|
|
||||||
this.height = 100
|
|
||||||
this.tiles = new Array(this.width * this.height)
|
|
||||||
for (let y = 0; y < this.height; y++) {
|
|
||||||
for (let x = 0; x < this.width; x++) {
|
|
||||||
this.tiles[this.get_index(x, y)] = TileType.LAND
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get_tile(x, y) {
|
|
||||||
return this.tiles[this.get_index(x, y)]
|
|
||||||
}
|
|
||||||
|
|
||||||
set_tile(x, y, type) {
|
|
||||||
this.tiles[this.get_index(x, y)] = type
|
|
||||||
}
|
|
||||||
|
|
||||||
get_index(x, y) {
|
|
||||||
return y * this.width + x
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function tile_index_to_coord(x, y) {
|
|
||||||
const x_stride = 2 * cos_60 + 2
|
|
||||||
const y_stride = sin_60
|
|
||||||
let x_offset = 0
|
|
||||||
if (y % 2 === 0) {
|
|
||||||
x_offset = 1 + cos_60
|
|
||||||
}
|
|
||||||
return [x * x_stride + x_offset, y * y_stride]
|
|
||||||
}
|
|
||||||
|
|
||||||
function draw_hex_tile(ctx, index, type) {
|
|
||||||
const center = tile_index_to_coord(index[0], index[1])
|
|
||||||
const radius = 1
|
|
||||||
|
|
||||||
ctx.save()
|
|
||||||
switch (type) {
|
|
||||||
case TileType.LAND:
|
|
||||||
ctx.fillStyle = 'green'
|
|
||||||
break
|
|
||||||
case TileType.WATER:
|
|
||||||
ctx.fillStyle = 'blue'
|
|
||||||
break
|
|
||||||
}
|
|
||||||
ctx.strokeStyle = 'black'
|
|
||||||
ctx.lineWidth = 1 / global_scale
|
|
||||||
ctx.translate(center[0], center[1])
|
|
||||||
ctx.scale(radius, radius)
|
|
||||||
|
|
||||||
ctx.save()
|
|
||||||
ctx.beginPath()
|
|
||||||
ctx.moveTo(-1, 0)
|
|
||||||
ctx.lineTo(-cos_60, sin_60)
|
|
||||||
ctx.lineTo(cos_60, sin_60)
|
|
||||||
ctx.lineTo(1, 0)
|
|
||||||
ctx.lineTo(cos_60, -sin_60)
|
|
||||||
ctx.lineTo(-cos_60, -sin_60)
|
|
||||||
ctx.lineTo(-1, 0)
|
|
||||||
ctx.restore()
|
|
||||||
|
|
||||||
ctx.fill()
|
|
||||||
ctx.stroke()
|
|
||||||
ctx.restore()
|
|
||||||
}
|
|
||||||
|
|
||||||
function draw_map(ctx) {
|
|
||||||
const tile_grid = new TileGrid(100, 100)
|
|
||||||
tile_grid.set_tile(2, 2, TileType.WATER)
|
|
||||||
tile_grid.set_tile(50, 10, TileType.WATER)
|
|
||||||
|
|
||||||
for (let y = 0; y < tile_grid.height; y++) {
|
|
||||||
for (let x = 0; x < tile_grid.width; x++) {
|
|
||||||
draw_hex_tile(ctx, [x, y], tile_grid.get_tile(x, y))
|
|
||||||
const coord = tile_index_to_coord(x, y)
|
|
||||||
|
|
||||||
if (global_scale > 15) {
|
|
||||||
ctx.save()
|
|
||||||
ctx.translate(coord[0], coord[1])
|
|
||||||
ctx.scale(1 / global_scale, 1 / global_scale)
|
|
||||||
ctx.font = '12px Arial'
|
|
||||||
ctx.fillStyle = 'black'
|
|
||||||
ctx.textAlign = 'center'
|
|
||||||
ctx.fillText(String([x, y]), 0, 0)
|
|
||||||
ctx.restore()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function draw_tower(ctx, index) {
|
|
||||||
const coord = tile_index_to_coord(index[0], index[1])
|
|
||||||
|
|
||||||
const radius_1 = 0.35
|
|
||||||
const radius_2 = 0.3
|
|
||||||
const height_1 = 1.1
|
|
||||||
const height_2 = 1.6
|
|
||||||
|
|
||||||
ctx.save()
|
|
||||||
ctx.lineWidth = 1 / global_scale
|
|
||||||
ctx.translate(coord[0], coord[1])
|
|
||||||
|
|
||||||
ctx.fillStyle = 'gray'
|
|
||||||
ctx.strokeStyle = 'black'
|
|
||||||
ctx.beginPath()
|
|
||||||
ctx.arc(0, 0, radius_1, 0, Math.PI)
|
|
||||||
ctx.lineTo(-radius_2, -height_1)
|
|
||||||
ctx.arc(0, -height_1, radius_2, Math.PI, 0, true)
|
|
||||||
ctx.lineTo(radius_1, 0)
|
|
||||||
ctx.fill()
|
|
||||||
ctx.stroke()
|
|
||||||
|
|
||||||
ctx.fillStyle = 'darkred'
|
|
||||||
ctx.strokeStyle = 'black'
|
|
||||||
ctx.beginPath()
|
|
||||||
ctx.arc(0, -height_1, radius_2, Math.PI, 0, true)
|
|
||||||
ctx.lineTo(0, -height_2)
|
|
||||||
ctx.lineTo(-radius_2, -height_1)
|
|
||||||
ctx.fill()
|
|
||||||
ctx.stroke()
|
|
||||||
|
|
||||||
ctx.restore()
|
|
||||||
}
|
|
||||||
|
|
||||||
function draw(ctx, offset, scale) {
|
|
||||||
global_scale = scale
|
|
||||||
global_offset = offset
|
|
||||||
|
|
||||||
ctx.translate(global_offset[0], global_offset[1])
|
|
||||||
ctx.scale(global_scale, global_scale)
|
|
||||||
|
|
||||||
draw_map(ctx)
|
|
||||||
|
|
||||||
let towers = [
|
|
||||||
[2, 0],
|
|
||||||
[2, 1],
|
|
||||||
[3, 1],
|
|
||||||
[2, 3],
|
|
||||||
[3, 3],
|
|
||||||
[2, 4]
|
|
||||||
]
|
|
||||||
for (let i = 0; i < 6; i++) {
|
|
||||||
draw_tower(ctx, towers[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function main(canvas) {
|
|
||||||
console.log('entrypoint function')
|
|
||||||
console.log(canvas)
|
|
||||||
if (canvas.getContext) {
|
|
||||||
const ctx = canvas.getContext('2d')
|
|
||||||
draw(ctx, [100, 100], 10)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function mousedown(evt) {
|
|
||||||
console.log(evt)
|
|
||||||
}
|
|
||||||
export function mouseup(evt) {
|
|
||||||
console.log(evt)
|
|
||||||
}
|
|
||||||
export function mousemove(evt) {
|
|
||||||
// console.log(evt)
|
|
||||||
}
|
|
||||||
export function click(evt) {
|
|
||||||
console.log(evt)
|
|
||||||
}
|
|
||||||
export function dblclick(evt) {
|
|
||||||
console.log(evt)
|
|
||||||
}
|
|
||||||
export function mouseenter(evt) {
|
|
||||||
console.log(evt)
|
|
||||||
}
|
|
||||||
export function mouseleave(evt) {
|
|
||||||
console.log(evt)
|
|
||||||
}
|
|
@ -1,93 +0,0 @@
|
|||||||
const path = require('path')
|
|
||||||
|
|
||||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
|
||||||
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin')
|
|
||||||
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
|
|
||||||
.BundleAnalyzerPlugin
|
|
||||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
|
||||||
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')
|
|
||||||
const WebpackBar = require('webpackbar')
|
|
||||||
|
|
||||||
module.exports = (env, argv) => {
|
|
||||||
const isEnvProduction = argv.mode === 'production'
|
|
||||||
const isEnvDevelopment = argv.mode === 'development'
|
|
||||||
|
|
||||||
return {
|
|
||||||
output: {
|
|
||||||
path: path.resolve(__dirname, 'build'),
|
|
||||||
filename: 'static/js/[name].[contenthash:8].js',
|
|
||||||
chunkFilename: 'static/js/[name].[contenthash:8].chunk.js'
|
|
||||||
},
|
|
||||||
devtool: isEnvProduction ? 'source-map' : 'cheap-module-source-map',
|
|
||||||
module: {
|
|
||||||
rules: [
|
|
||||||
// process javascript with babel
|
|
||||||
{
|
|
||||||
test: /\.js$/,
|
|
||||||
exclude: /node_modules/,
|
|
||||||
loader: 'babel-loader'
|
|
||||||
},
|
|
||||||
// process css. css modules are enabled.
|
|
||||||
{
|
|
||||||
test: /\.css$/,
|
|
||||||
use: [
|
|
||||||
isEnvDevelopment && 'style-loader',
|
|
||||||
isEnvProduction && MiniCssExtractPlugin.loader,
|
|
||||||
{
|
|
||||||
loader: 'css-loader',
|
|
||||||
options: {
|
|
||||||
modules: {
|
|
||||||
localIdentName: isEnvProduction
|
|
||||||
? '[hash:base64]'
|
|
||||||
: '[path][name]__[local]'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'postcss-loader'
|
|
||||||
].filter(Boolean)
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
optimization: {
|
|
||||||
minimize: isEnvProduction,
|
|
||||||
splitChunks: {
|
|
||||||
chunks: 'all',
|
|
||||||
name: false
|
|
||||||
},
|
|
||||||
runtimeChunk: {
|
|
||||||
name: entrypoint => `runtime-${entrypoint.name}`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
// creat an index.html
|
|
||||||
new HtmlWebpackPlugin(),
|
|
||||||
// show a progress bar
|
|
||||||
new WebpackBar(),
|
|
||||||
// create a report.html for bundle size
|
|
||||||
// extract css to css files
|
|
||||||
isEnvProduction &&
|
|
||||||
new MiniCssExtractPlugin({
|
|
||||||
filename: 'static/css/[name].[contenthash:8].css',
|
|
||||||
chunkFilename: 'static/css/[name].[contenthash:8].chunk.css'
|
|
||||||
}),
|
|
||||||
// use cssnano to minify css
|
|
||||||
isEnvProduction &&
|
|
||||||
new OptimizeCssAssetsPlugin({
|
|
||||||
cssProcessorOptions: {
|
|
||||||
map: { inline: false, annotation: true }
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
isEnvProduction &&
|
|
||||||
new BundleAnalyzerPlugin({
|
|
||||||
analyzerMode: 'static',
|
|
||||||
openAnalyzer: false
|
|
||||||
})
|
|
||||||
// hot reload not working right now
|
|
||||||
// isEnvDevelopment &&
|
|
||||||
// new ReactRefreshWebpackPlugin({ disableRefreshCheck: true })
|
|
||||||
].filter(Boolean),
|
|
||||||
devServer: {
|
|
||||||
stats: 'minimal'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue