diff --git a/backend/config.yaml b/backend/config.yaml index 313515b..024f340 100644 --- a/backend/config.yaml +++ b/backend/config.yaml @@ -33,7 +33,8 @@ max_energy_per_player: 0.2 power_plant: resources_per_tick: 1 - sell_coeff: 0.5 + price_coeff: 0.5 + sell_coeff: 0.7 base_prices: coal: 3000 diff --git a/backend/db/migration.py b/backend/db/migration.py index 9502ef3..3972520 100644 --- a/backend/db/migration.py +++ b/backend/db/migration.py @@ -1,7 +1,7 @@ import os import pandas as pd from db.db import database -from model import Team, Player, PowerPlant, PowerPlantType, Game, Datasets, DatasetData +from model import Team, Player, Game, Datasets, DatasetData from datetime import datetime from config import config from logger import logger @@ -17,20 +17,20 @@ async def fill_tables(): dataset_id = datasets[0].dataset_id not_nat_game_id = await Game.create( - game_name="Stalna igra", - is_contest=False, - bots="dummy:3;resource_bot:1", - dataset_id=dataset_id, + game_name="Stalna igra", + is_contest=False, + bots="dummy:3;resource_bot:1", + dataset_id=dataset_id, start_time=datetime.now(), total_ticks=2300, tick_time=3000) nat_game_id = await Game.create( - game_name="Natjecanje", - is_contest=True, - bots="dummy:2;resource_bot:1", - dataset_id=dataset_id, - start_time=datetime.now(), - total_ticks=100, + game_name="Natjecanje", + is_contest=True, + bots="dummy:2;resource_bot:1", + dataset_id=dataset_id, + start_time=datetime.now(), + total_ticks=100, tick_time=1000) for game_id in [not_nat_game_id, nat_game_id]: @@ -38,17 +38,15 @@ async def fill_tables(): await Player.create(player_name="Kruno", is_active=True, is_bot=False, game_id=game_id, team_id=k_team_id) await Player.create(player_name="Zvone", is_active=True, is_bot=False, game_id=game_id, team_id=z_team_id) - await PowerPlant.create(type=PowerPlantType.COAL, player_id=1, price=1, powered_on=True) - logger.info("Filled database with dummy data") async def delete_tables(): - await database.execute('TRUNCATE power_plants, orders, players, games, teams, market, datasets, dataset_data CASCADE') + await database.execute('TRUNCATE orders, players, games, teams, market, datasets, dataset_data CASCADE') async def drop_tables(): - for table_name in ["power_plants", "orders", "players", "games", "teams", "market", "datasets", "dataset_data"]: + for table_name in ["orders", "players", "games", "teams", "market", "datasets", "dataset_data"]: await database.execute(f'DROP TABLE IF EXISTS {table_name} CASCADE') @@ -101,22 +99,31 @@ async def run_migrations(): biomass INT NOT NULL DEFAULT 0, gas INT NOT NULL DEFAULT 0, oil INT NOT NULL DEFAULT 0, + + coal_plants_owned INT NOT NULL DEFAULT 0, + uranium_plants_owned INT NOT NULL DEFAULT 0, + biomass_plants_owned INT NOT NULL DEFAULT 0, + gas_plants_owned INT NOT NULL DEFAULT 0, + oil_plants_owned INT NOT NULL DEFAULT 0, + geothermal_plants_owned INT NOT NULL DEFAULT 0, + wind_plants_owned INT NOT NULL DEFAULT 0, + solar_plants_owned INT NOT NULL DEFAULT 0, + hydro_plants_owned INT NOT NULL DEFAULT 0, + + coal_plants_powered INT NOT NULL DEFAULT 0, + uranium_plants_powered INT NOT NULL DEFAULT 0, + biomass_plants_powered INT NOT NULL DEFAULT 0, + gas_plants_powered INT NOT NULL DEFAULT 0, + oil_plants_powered INT NOT NULL DEFAULT 0, + geothermal_plants_powered INT NOT NULL DEFAULT 0, + wind_plants_powered INT NOT NULL DEFAULT 0, + solar_plants_powered INT NOT NULL DEFAULT 0, + hydro_plants_powered INT NOT NULL DEFAULT 0, FOREIGN KEY (game_id) REFERENCES games(game_id), FOREIGN KEY (team_id) REFERENCES teams(team_id) )''') - await database.execute(''' - CREATE TABLE IF NOT EXISTS power_plants ( - power_plant_id SERIAL PRIMARY KEY, - type INT NOT NULL, - player_id INT NOT NULL, - price INT NOT NULL, - powered_on BOOLEAN NOT NULL DEFAULT false, - temperature REAL NOT NULL DEFAULT 0, - FOREIGN KEY (player_id) REFERENCES players(player_id) - )''') - await database.execute(''' CREATE TABLE IF NOT EXISTS orders ( order_id SERIAL PRIMARY KEY, @@ -208,21 +215,21 @@ async def run_migrations(): i = 0 for index, row in df.iterrows(): await DatasetData.create(dataset_id=dataset_id, - tick=i, - date=datetime.strptime( - row["date"], "%Y-%m-%d %H:%M:%S"), - coal=row["COAL"], - uranium=row["URANIUM"], - biomass=row["BIOMASS"], - gas=row["GAS"], - oil=row["OIL"], - geothermal=row["GEOTHERMAL"], - wind=row["WIND"], - solar=row["SOLAR"], - hydro=row["HYDRO"], - energy_demand=row["ENERGY_DEMAND"], - max_energy_price=row["MAX_ENERGY_PRICE"] - ) + tick=i, + date=datetime.strptime( + row["date"], "%Y-%m-%d %H:%M:%S"), + coal=row["COAL"], + uranium=row["URANIUM"], + biomass=row["BIOMASS"], + gas=row["GAS"], + oil=row["OIL"], + geothermal=row["GEOTHERMAL"], + wind=row["WIND"], + solar=row["SOLAR"], + hydro=row["HYDRO"], + energy_demand=row["ENERGY_DEMAND"], + max_energy_price=row["MAX_ENERGY_PRICE"] + ) i += 1 logger.info(f"Added dataset {x}") logger.info("Migrated database") diff --git a/backend/game/fixtures/fixtures.py b/backend/game/fixtures/fixtures.py index 9bb2644..f65ce29 100644 --- a/backend/game/fixtures/fixtures.py +++ b/backend/game/fixtures/fixtures.py @@ -1,8 +1,7 @@ -from pprint import pprint import pandas as pd from game.tick import TickData, Ticker from game.tick.ticker import GameData -from model import Order, Player, Resource, Game, PowerPlant, PowerPlantType, OrderSide +from model import Order, Player, Resource, Game, PowerPlantType, OrderSide from game.market import ResourceMarket, EnergyMarket import pytest from model.dataset_data import DatasetData @@ -73,7 +72,7 @@ def get_ticker(players) -> Ticker: @pytest.fixture def get_tick_data(game_id): - def get_tick_data(power_plants, markets, players, user_cancelled_orders=[], pending_orders=[], updated_orders=[], coal=100, energy_demand=100, max_energy_price=100) -> TickData: + def get_tick_data(markets, players, user_cancelled_orders=[], pending_orders=[], updated_orders=[], coal=100, energy_demand=100, max_energy_price=100) -> TickData: tick_data = TickData( game=Game( game_id=game_id, @@ -103,7 +102,6 @@ def get_tick_data(power_plants, markets, players, user_cancelled_orders=[], pend energy_demand=energy_demand, max_energy_price=max_energy_price ), - power_plants=power_plants, markets=markets, players=players, user_cancelled_orders=user_cancelled_orders, @@ -190,25 +188,6 @@ def energy_market(): return EnergyMarket() -@pytest.fixture -def get_power_plant(): - plant_id = 0 - - def get_power_plant(player_id: int, type: PowerPlantType, powered_on: int = True, **kwargs): - nonlocal plant_id - power_plant = PowerPlant( - power_plant_id=plant_id, - player_id=player_id, - type=type, - price=100, - powered_on=powered_on, - **kwargs - ) - plant_id += 1 - return power_plant - return get_power_plant - - @pytest.fixture def get_energy_market(): def get_energy_market() -> EnergyMarket: diff --git a/backend/game/market/energy_market.py b/backend/game/market/energy_market.py index 44a2cca..d35fe72 100644 --- a/backend/game/market/energy_market.py +++ b/backend/game/market/energy_market.py @@ -2,7 +2,6 @@ from model.order import Order from config import config from model.player import Player -from model.power_plant import PowerPlant class EnergyMarket: diff --git a/backend/game/market/resource_market.py b/backend/game/market/resource_market.py index fa0d201..9ad9eed 100644 --- a/backend/game/market/resource_market.py +++ b/backend/game/market/resource_market.py @@ -1,4 +1,3 @@ -from pprint import pprint from game.orderbook.orderbook import OrderBook from game.price_tracker.price_tracker import PriceTracker from model import Resource, Trade, Order diff --git a/backend/game/market/test_resource_market.py b/backend/game/market/test_resource_market.py index 1e0011d..dbea4ee 100644 --- a/backend/game/market/test_resource_market.py +++ b/backend/game/market/test_resource_market.py @@ -1,4 +1,3 @@ -from pprint import pprint from model import Order, Player, OrderSide, OrderStatus from . import ResourceMarket, EnergyMarket from game.fixtures.fixtures import * diff --git a/backend/game/tick/test_tick_fixtures.py b/backend/game/tick/test_tick_fixtures.py index e78ab81..bb4afe4 100644 --- a/backend/game/tick/test_tick_fixtures.py +++ b/backend/game/tick/test_tick_fixtures.py @@ -1,7 +1,7 @@ from datetime import datetime import pytest from game.tick import TickData, Ticker, GameData -from model import Game, Player, PowerPlant, Order, OrderStatus, Resource +from model import Game, Player, Order, OrderStatus, Resource @pytest.fixture @@ -23,8 +23,8 @@ def sample_game(): @pytest.fixture def sample_players(): return { - 1: Player(player_id=1, game_id=1, player_name="Player 1", energy=0, team_id=1), - 2: Player(player_id=2, game_id=1, player_name="Player 2", energy=0, team_id=1) + 1: Player(player_id=1, game_id=1, player_name="Player 1", energy=0, team_id=1, wind_plants_owned=2, wind_plants_powered=2), + 2: Player(player_id=2, game_id=1, player_name="Player 2", energy=0, team_id=1, coal=100, coal_plants_powered=2, coal_plants_owned=2) } @@ -36,22 +36,6 @@ def ticker(sample_game, sample_players): return t -@pytest.fixture -def sample_power_plants(): - return { - 1: [ - PowerPlant(power_plant_id=1, player_id=1, type=1, price=100, - temperature=0.5, powered_on=True), - PowerPlant(power_plant_id=2, player_id=1, type=2, price=100, - temperature=0.6, powered_on=True) - ], - 2: [ - PowerPlant(power_plant_id=3, player_id=2, type=1, price=100, - temperature=0.7, powered_on=True) - ] - } - - @pytest.fixture def sample_pending_orders(): return [ @@ -74,7 +58,7 @@ def sample_user_cancelled_orders(): @pytest.fixture def sample_dataset_row(): - return {"energy_demand": 100, "max_energy_price": 50} + return {"energy_demand": 100, "max_energy_price": 50, "coal": 100, "oil": 100, "uranium": 100, 'biomass': 100, 'gas': 100, 'geothermal': 100, 'solar': 100, 'wind': 100, 'hydro': 100} @pytest.fixture @@ -82,7 +66,6 @@ def tick_data(sample_game, sample_players): return TickData( game=sample_game, players=sample_players, - power_plants={}, markets={}, bots=[], dataset_row={}, diff --git a/backend/game/tick/test_ticker_bots.py b/backend/game/tick/test_ticker_bots.py index 82e4d78..d5c3116 100644 --- a/backend/game/tick/test_ticker_bots.py +++ b/backend/game/tick/test_ticker_bots.py @@ -37,7 +37,7 @@ async def test_run_bots(get_tick_data): # Set the bots for the game ticker.game_data[game.game_id] = GameData(game, players) - tick_data = get_tick_data(power_plants={}, markets={}, players={}) + tick_data = get_tick_data(markets={}, players={}) # Run the method being tested await ticker.run_bots(tick_data) @@ -45,4 +45,5 @@ async def test_run_bots(get_tick_data): # Assertions # Ensure Bot.run is called once for each bot assert mock_run.call_count == len(bots) - mock_run.assert_called_with(tick_data) # Ensure Bot.run is called with no arguments + # Ensure Bot.run is called with no arguments + mock_run.assert_called_with(tick_data) diff --git a/backend/game/tick/test_ticker_db_operations.py b/backend/game/tick/test_ticker_db_operations.py index 7279242..d0c5253 100644 --- a/backend/game/tick/test_ticker_db_operations.py +++ b/backend/game/tick/test_ticker_db_operations.py @@ -3,31 +3,45 @@ from unittest.mock import patch from datetime import datetime from model import Order, OrderStatus, Resource +from model.dataset_data import DatasetData from model.order_types import OrderSide, OrderType from game.tick import Ticker, TickData from tick.test_tick_fixtures import * -@patch('model.Player.list') -@patch('model.PowerPlant.list') -@patch('model.Order.list') -@patch('model.DatasetData.get') @pytest.mark.asyncio -async def test_get_tick_data(mock_dataset_get, mock_order_list, mock_powerplant_list, mock_player_list, ticker, sample_game, sample_players, sample_power_plants, sample_pending_orders, sample_user_cancelled_orders, sample_dataset_row): - mock_player_list.return_value = sample_players.values() - mock_powerplant_list.side_effect = lambda player_id: sample_power_plants[player_id] - mock_order_list.side_effect = lambda **kwargs: sample_pending_orders if kwargs.get( - "order_status") == OrderStatus.PENDING else sample_user_cancelled_orders - mock_dataset_get.return_value = sample_dataset_row +async def test_get_tick_data(sample_game, sample_players, sample_pending_orders, sample_user_cancelled_orders, sample_dataset_row): + # Setup ticker + ticker = Ticker() + ticker.game_data[sample_game.game_id] = GameData( + sample_game, sample_players) - tick_data = await ticker.get_tick_data(sample_game) + # Mocking database interaction + async def mock_list_players(*args, **kwargs): + return [sample_players[1], sample_players[2]] - assert len(tick_data.players) == len(sample_players) - assert len(tick_data.power_plants) == len(sample_power_plants) - assert len(tick_data.pending_orders) == len(sample_pending_orders) - assert len(tick_data.user_cancelled_orders) == len( - sample_user_cancelled_orders) - assert tick_data.dataset_row == sample_dataset_row + async def mock_list_orders(*args, **kwargs): + if kwargs.get('order_status') == OrderStatus.PENDING: + return sample_pending_orders + elif kwargs.get('order_status') == OrderStatus.USER_CANCELLED: + return sample_user_cancelled_orders + + async def mock_get_dataset_data(*args, **kwargs): + return sample_dataset_row + + with patch('model.Player.list', new=mock_list_players), patch('model.Order.list', new=mock_list_orders), patch('model.DatasetData.get', new=mock_get_dataset_data): + # Execute get_tick_data method + tick_data = await ticker.get_tick_data(sample_game) + + # Assertions + assert len(tick_data.players) == 2 + # Assuming 2 pending orders in sample_pending_orders fixture + assert len(tick_data.pending_orders) == 2 + # Assuming 2 user cancelled orders in sample_user_cancelled_orders fixture + assert len(tick_data.user_cancelled_orders) == 2 + assert tick_data.dataset_row == sample_dataset_row + # Assuming all resources have markets created + assert len(tick_data.markets) == len(Resource) @pytest.fixture @@ -41,14 +55,12 @@ def sample_update_orders(): @patch('model.Player.update') -@patch('model.PowerPlant.update') @patch('model.Order.update') @pytest.mark.asyncio -async def test_save_tick_data(mock_order_update, mock_powerplant_update, mock_player_update, ticker, sample_game, sample_players, sample_power_plants, sample_pending_orders, sample_user_cancelled_orders, sample_dataset_row, sample_update_orders): +async def test_save_tick_data(mock_order_update, mock_player_update, ticker, sample_game, sample_players, sample_pending_orders, sample_user_cancelled_orders, sample_dataset_row, sample_update_orders): tick_data = TickData( game=sample_game, players=sample_players, - power_plants=sample_power_plants, pending_orders=sample_pending_orders, user_cancelled_orders=sample_user_cancelled_orders, dataset_row=sample_dataset_row, @@ -60,8 +72,6 @@ async def test_save_tick_data(mock_order_update, mock_powerplant_update, mock_pl await ticker.save_tick_data(tick_data) assert mock_player_update.call_count == len(sample_players) - assert mock_powerplant_update.call_count == len(sample_power_plants[1]) + len( - sample_power_plants[2]) assert mock_order_update.call_count == len(sample_update_orders) diff --git a/backend/game/tick/test_ticker_run_all_game_ticks.py b/backend/game/tick/test_ticker_run_all_game_ticks.py index c9f9bfd..0a921d4 100644 --- a/backend/game/tick/test_ticker_run_all_game_ticks.py +++ b/backend/game/tick/test_ticker_run_all_game_ticks.py @@ -1,3 +1,4 @@ +from databases import Database import pytest from datetime import datetime, timedelta from unittest.mock import patch @@ -50,11 +51,12 @@ async def test_run_all_game_ticks_game_started(): ticker = Ticker() ticker.game_data[game.game_id] = GameData(game, {}) - # Execute - with patch.object(Game, 'list') as mock_game_list: - mock_game_list.return_value = [game] - with patch.object(Ticker, 'run_game_tick') as mock_run_game_tick: - await ticker.run_all_game_ticks() + with patch.object(Database, 'transaction') as mock_transaction: + with patch.object(Database, 'execute') as mock_execute: + with patch.object(Game, 'list') as mock_game_list: + mock_game_list.return_value = [game] + with patch.object(Ticker, 'run_game_tick') as mock_run_game_tick: + await ticker.run_all_game_ticks() mock_run_game_tick.assert_called_once_with(game) @@ -70,10 +72,12 @@ async def test_run_all_game_ticks_game_data_not_exist(): ticker = Ticker() # Execute - with patch.object(Game, 'list') as mock_game_list: - mock_game_list.return_value = [game] - with patch.object(Ticker, 'run_game_tick') as mock_run_game_tick: - await ticker.run_all_game_ticks() + with patch.object(Database, 'transaction') as mock_transaction: + with patch.object(Database, 'execute') as mock_execute: + with patch.object(Game, 'list') as mock_game_list: + mock_game_list.return_value = [game] + with patch.object(Ticker, 'run_game_tick') as mock_run_game_tick: + await ticker.run_all_game_ticks() # Verify assert game.game_id in ticker.game_data diff --git a/backend/game/tick/test_ticker_run_electricity_market.py b/backend/game/tick/test_ticker_run_electricity_market.py index 47eb6c6..5b6cd2e 100644 --- a/backend/game/tick/test_ticker_run_electricity_market.py +++ b/backend/game/tick/test_ticker_run_electricity_market.py @@ -1,5 +1,3 @@ -from copy import deepcopy -from pprint import pprint import pytest from game.tick import TickData from game.fixtures.fixtures import * @@ -8,13 +6,12 @@ from config import config -def test_successful(get_tick_data, get_ticker, get_player, get_power_plant): +def test_successful(get_tick_data, get_ticker, get_player): player1 = get_player(money=0, coal=0, energy=10, energy_price=100) player_dict = get_player_dict([player1]) - tick_data = get_tick_data(power_plants={}, - markets=[], + tick_data = get_tick_data(markets=[], players=player_dict, energy_demand=100, max_energy_price=100 @@ -28,13 +25,12 @@ def test_successful(get_tick_data, get_ticker, get_player, get_power_plant): assert energy_sold == {player1.player_id: 10} -def test_player_price_too_high(get_tick_data, get_ticker, get_player, get_power_plant): +def test_player_price_too_high(get_tick_data, get_ticker, get_player): player1 = get_player(money=0, coal=0, energy=10, energy_price=101) player_dict = get_player_dict([player1]) - tick_data = get_tick_data(power_plants={}, - markets=[], + tick_data = get_tick_data(markets=[], players=player_dict, energy_demand=100, max_energy_price=100 @@ -48,7 +44,7 @@ def test_player_price_too_high(get_tick_data, get_ticker, get_player, get_power_ assert energy_sold == {} -def test_player_over_max_demand(get_tick_data, get_ticker, get_player, get_power_plant): +def test_player_over_max_demand(get_tick_data, get_ticker, get_player): demand = 100 max_sold_per_player = int(config["max_energy_per_player"] * demand) @@ -57,8 +53,7 @@ def test_player_over_max_demand(get_tick_data, get_ticker, get_player, get_power money=0, coal=0, energy=max_sold_per_player + 1, energy_price=99) player_dict = get_player_dict([player1]) - tick_data = get_tick_data(power_plants={}, - markets=[], + tick_data = get_tick_data(markets=[], players=player_dict, energy_demand=demand, max_energy_price=100 @@ -72,13 +67,12 @@ def test_player_over_max_demand(get_tick_data, get_ticker, get_player, get_power assert energy_sold == {player1.player_id: max_sold_per_player} -def test_player_no_energy(get_tick_data, get_ticker, get_player, get_power_plant): +def test_player_no_energy(get_tick_data, get_ticker, get_player): player1 = get_player(money=0, coal=0, energy=0, energy_price=1) player_dict = get_player_dict([player1]) - tick_data = get_tick_data(power_plants={}, - markets=[], + tick_data = get_tick_data(markets=[], players=player_dict, energy_demand=100, max_energy_price=100 @@ -92,7 +86,7 @@ def test_player_no_energy(get_tick_data, get_ticker, get_player, get_power_plant assert energy_sold == {} -def test_demand_filled(get_tick_data, get_ticker, get_player, get_power_plant): +def test_demand_filled(get_tick_data, get_ticker, get_player): demand = 101 players = [get_player(money=0, coal=0, energy=50, energy_price=90), @@ -109,8 +103,7 @@ def test_demand_filled(get_tick_data, get_ticker, get_player, get_power_plant): player_dict = get_player_dict(players) - tick_data = get_tick_data(power_plants={}, - markets=[], + tick_data = get_tick_data(markets=[], players=player_dict, energy_demand=demand, max_energy_price=100 diff --git a/backend/game/tick/test_ticker_run_markets.py b/backend/game/tick/test_ticker_run_markets.py index 578f042..7981f23 100644 --- a/backend/game/tick/test_ticker_run_markets.py +++ b/backend/game/tick/test_ticker_run_markets.py @@ -1,5 +1,4 @@ from copy import deepcopy -from pprint import pprint import pytest from game.tick import TickData from game.fixtures.fixtures import * @@ -7,7 +6,7 @@ from model.player import Player -def test_run_markets_no_match(get_tick_data, get_order, get_ticker, get_player, get_power_plant, coal_market): +def test_run_markets_no_match(get_tick_data, get_order, get_ticker, get_player, coal_market): order1 = get_order(player_id=1, price=5, size=50, order_side=OrderSide.BUY, tick=1) @@ -31,7 +30,7 @@ def test_run_markets_no_match(get_tick_data, get_order, get_ticker, get_player, ], updated_orders={}, players=player_dict, - power_plants={}, + markets={ Resource.coal.value: coal_market(player_dict) } @@ -44,7 +43,7 @@ def test_run_markets_no_match(get_tick_data, get_order, get_ticker, get_player, assert len(tick_data.updated_orders) == 2 -def test_run_markets_match(get_tick_data, get_order, get_ticker, get_player, get_power_plant, coal_market): +def test_run_markets_match(get_tick_data, get_order, get_ticker, get_player, coal_market): order1 = get_order(player_id=1, price=5, size=50, order_side=OrderSide.BUY, tick=1) @@ -65,7 +64,7 @@ def test_run_markets_match(get_tick_data, get_order, get_ticker, get_player, get ], updated_orders={}, players=player_dict, - power_plants={}, + markets={ Resource.coal.value: coal_market(player_dict) } @@ -87,7 +86,7 @@ def test_run_markets_match(get_tick_data, get_order, get_ticker, get_player, get assert len(tick_data.updated_orders) == 2 -def test_run_markets_match_insufficient_funds(get_tick_data, get_order, get_ticker, get_player, get_power_plant, coal_market): +def test_run_markets_match_insufficient_funds(get_tick_data, get_order, get_ticker, get_player, coal_market): order1 = get_order(player_id=1, price=5, size=50, order_side=OrderSide.BUY, tick=1) @@ -108,7 +107,6 @@ def test_run_markets_match_insufficient_funds(get_tick_data, get_order, get_tick ], updated_orders={}, players=player_dict, - power_plants={}, markets={ Resource.coal.value: coal_market(player_dict) } @@ -130,7 +128,7 @@ def test_run_markets_match_insufficient_funds(get_tick_data, get_order, get_tick assert len(tick_data.updated_orders) == 2 -def test_run_markets_match_insufficient_resources(get_tick_data, get_order, get_ticker, get_player, get_power_plant, coal_market): +def test_run_markets_match_insufficient_resources(get_tick_data, get_order, get_ticker, get_player, coal_market): order1 = get_order(player_id=1, price=5, size=50, order_side=OrderSide.BUY, tick=1) @@ -151,7 +149,6 @@ def test_run_markets_match_insufficient_resources(get_tick_data, get_order, get_ ], updated_orders={}, players=player_dict, - power_plants={}, markets={ Resource.coal.value: coal_market(player_dict) } @@ -173,7 +170,7 @@ def test_run_markets_match_insufficient_resources(get_tick_data, get_order, get_ assert len(tick_data.updated_orders) == 2 -def test_run_markets_cancel(get_tick_data, get_order, get_ticker, get_player, get_power_plant, coal_market): +def test_run_markets_cancel(get_tick_data, get_order, get_ticker, get_player, coal_market): order1 = get_order(player_id=1, price=5, size=50, order_side=OrderSide.BUY, tick=1) @@ -194,7 +191,7 @@ def test_run_markets_cancel(get_tick_data, get_order, get_ticker, get_player, ge pending_orders=[], updated_orders={}, players=player_dict, - power_plants={}, + markets={ Resource.coal.value: coal_market(player_dict) } diff --git a/backend/game/tick/test_ticker_run_power_plants.py b/backend/game/tick/test_ticker_run_power_plants.py index 43094b6..341bc68 100644 --- a/backend/game/tick/test_ticker_run_power_plants.py +++ b/backend/game/tick/test_ticker_run_power_plants.py @@ -1,100 +1,30 @@ -from copy import deepcopy -from pprint import pprint import pytest -from game.tick import TickData -from game.fixtures.fixtures import * -from model.order_types import OrderSide, OrderStatus -from model.player import Player +from game.tick.ticker import GameData +from tick import Ticker, TickData +from model import Game, Player, PowerPlantType, Resource, DatasetData +from .test_tick_fixtures import sample_game, sample_players, ticker, sample_dataset_row, tick_data -def test_run_power_plants_no_resource(get_tick_data, get_ticker, get_player, get_power_plant): - - player1 = get_player(money=1000, coal=0, energy=0) - player_dict = get_player_dict([player1]) - - power_plant1 = get_power_plant( - player_id=player1.player_id, type=PowerPlantType.COAL, powered_on=True) - - tick_data = get_tick_data(power_plants={ - player1.player_id: [power_plant1]}, - markets=[], - players=player_dict) - - ticker = get_ticker(players=player_dict) - - ticker.run_power_plants(tick_data) - - assert player1.energy == 0 - assert power_plant1.temperature == 0.0 - assert power_plant1.powered_on == False - - -def test_run_power_plants_heating_up(get_tick_data, get_ticker, get_player, get_power_plant): - - player1 = get_player(money=1000, coal=1, energy=0) - player_dict = get_player_dict([player1]) - - power_plant1 = get_power_plant( - player_id=player1.player_id, type=PowerPlantType.COAL, powered_on=True) - - tick_data = get_tick_data(power_plants={ - player1.player_id: [power_plant1]}, - markets=[], - players=player_dict) - - ticker = get_ticker(players=player_dict) - - ticker.run_power_plants(tick_data) - - assert player1.energy == 0 - assert player1.coal == 0 - assert power_plant1.temperature == PowerPlantType.COAL.get_new_temp( - 0.0, True) - assert power_plant1.powered_on == True - - -def test_run_power_plants_producing(get_tick_data, get_ticker, get_player, get_power_plant): - player1 = get_player(money=1000, coal=1, energy=0) - player_dict = get_player_dict([player1]) - - power_plant1 = get_power_plant( - player_id=player1.player_id, type=PowerPlantType.COAL, powered_on=True, temperature=1.0) - - tick_data = get_tick_data(power_plants={ - player1.player_id: [power_plant1]}, - markets=[], - players=player_dict) - - ticker = get_ticker(players=player_dict) - - ticker.run_power_plants(tick_data) - - assert player1.energy == power_plant1.get_produced_energy( - tick_data.dataset_row) - assert player1.coal == 0 - assert power_plant1.temperature == PowerPlantType.COAL.get_new_temp( - 1.0, True) - assert power_plant1.powered_on == True - - -def test_run_power_plants_renewable(get_tick_data, get_ticker, get_player, get_power_plant): - player1 = get_player(money=0, energy=0) - player_dict = get_player_dict([player1]) +import pytest +from model import PowerPlantType +from game.tick import TickData, Ticker - power_plant1 = get_power_plant( - player_id=player1.player_id, type=PowerPlantType.WIND, powered_on=True, temperature=1.0) +# Test case using fixtures - tick_data = get_tick_data(power_plants={ - player1.player_id: [power_plant1]}, - markets=[], - players=player_dict) - ticker = get_ticker(players=player_dict) +@pytest.mark.asyncio +async def test_run_power_plants(sample_game, sample_players, tick_data, sample_dataset_row): + ticker = Ticker() + ticker.game_data[sample_game.game_id] = GameData( + sample_game, sample_players) + tick_data.dataset_row = sample_dataset_row - ticker.run_power_plants(tick_data) + updated_tick_data = ticker.run_power_plants(tick_data) - assert player1.energy == power_plant1.get_produced_energy( - tick_data.dataset_row) - assert power_plant1.temperature == PowerPlantType.WIND.get_new_temp( - 1.0, True) - assert power_plant1.powered_on == True + for player_id, player in updated_tick_data.players.items(): + total_energy = sum([ + player[plant_type.name.lower() + "_plants_powered"] * + plant_type.get_produced_energy(updated_tick_data.dataset_row) + for plant_type in PowerPlantType + ]) + assert player.energy == total_energy diff --git a/backend/game/tick/test_ticker_test_run_game_tick.py b/backend/game/tick/test_ticker_test_run_game_tick.py index 48223e7..a807965 100644 --- a/backend/game/tick/test_ticker_test_run_game_tick.py +++ b/backend/game/tick/test_ticker_test_run_game_tick.py @@ -8,7 +8,7 @@ @pytest.mark.asyncio async def test_run_game_tick( sample_game, sample_players, tick_data, - sample_power_plants, sample_pending_orders, + sample_pending_orders, sample_user_cancelled_orders, sample_dataset_row ): with patch.object(Ticker, 'get_tick_data', return_value=tick_data), \ diff --git a/backend/game/tick/tick_data.py b/backend/game/tick/tick_data.py index 3c2b07c..1196df0 100644 --- a/backend/game/tick/tick_data.py +++ b/backend/game/tick/tick_data.py @@ -1,5 +1,5 @@ from dataclasses import dataclass, field -from model import Player, PowerPlant, Game, Order +from model import Player, Game, Order from game.market import ResourceMarket from game.bots.bot import Bot @@ -8,7 +8,6 @@ class TickData: game: Game players: dict[int, Player] - power_plants: dict[int, list[PowerPlant]] markets: dict[int, ResourceMarket] bots: list[Bot] @@ -16,4 +15,4 @@ class TickData: pending_orders: list[Order] = field(default_factory=list) user_cancelled_orders: list[Order] = field(default_factory=list) - updated_orders: dict[int, Order] = field(default_factory=dict) \ No newline at end of file + updated_orders: dict[int, Order] = field(default_factory=dict) diff --git a/backend/game/tick/ticker.py b/backend/game/tick/ticker.py index 718f023..5c5210a 100644 --- a/backend/game/tick/ticker.py +++ b/backend/game/tick/ticker.py @@ -2,16 +2,16 @@ from datetime import datetime from typing import Tuple import pandas as pd -from model import Player, PowerPlant, Game, Order, OrderStatus, Resource, DatasetData, OrderSide, OrderType, PowerPlantType +from model import Player, PowerPlantType, Game, Order, OrderStatus, Resource, DatasetData, OrderSide, OrderType from game.market import ResourceMarket, EnergyMarket from game.bots import Bots from .tick_data import TickData from logger import logger +from db import database class GameData: def __init__(self, game: Game, players: dict[int, Player]): - self.players: dict[int, Player] = players self.markets: dict[int, ResourceMarket] = { resource.value: ResourceMarket(resource, players) for resource in Resource @@ -19,6 +19,7 @@ def __init__(self, game: Game, players: dict[int, Player]): self.energy_market = EnergyMarket() self.bots = Bots.create_bots(game.bots) + class Ticker: def __init__(self): self.game_data: dict[int, GameData] = {} @@ -38,27 +39,38 @@ async def run_all_game_ticks(self): await Game.update(game_id=game.game_id, is_finished=True) if self.game_data.get(game.game_id) is not None: del self.game_data[game.game_id] - logger.info(f"Finished game ({game.game_id}) {game.game_name}") + logger.info( + f"Finished game ({game.game_id}) {game.game_name}") except Exception as e: - logger.critical(f"Failed finishing game ({game.game_id}) {game.current_tick} with error: " + str(e)) + logger.critical( + f"Failed finishing game ({game.game_id}) {game.current_tick} with error: " + str(e)) continue if self.game_data.get(game.game_id) is None: try: - logger.info(f"Starting game ({game.game_id}) {game.game_name}") + logger.info( + f"Starting game ({game.game_id}) {game.game_name}") self.game_data[game.game_id] = GameData(game, {}) except Exception as e: - logger.critical(f"Failed creating game ({game.game_id}) {game.current_tick} with error: " + str(e)) + logger.critical( + f"Failed creating game ({game.game_id}) {game.current_tick} with error: " + str(e)) continue - + try: - await self.run_game_tick(game) + + async with database.transaction(): + await database.execute( + f"LOCK TABLE orders, players IN SHARE ROW EXCLUSIVE MODE") + + await self.run_game_tick(game) + except Exception as e: - logger.critical(f"({game.game_id}) {game.game_name} tick {game.current_tick} failed with error: " + str(e)) - + logger.critical( + f"({game.game_id}) {game.game_name} tick {game.current_tick} failed with error: " + str(e)) async def run_game_tick(self, game: Game): - logger.debug(f"({game.game_id}) {game.game_name}: {game.current_tick} tick") + logger.debug( + f"({game.game_id}) {game.game_name}: {game.current_tick} tick") tick_data = await self.get_tick_data(game) tick_data = self.run_markets(tick_data) tick_data = self.run_power_plants(tick_data) @@ -78,11 +90,6 @@ async def get_tick_data(self, game: Game) -> TickData: for player in await Player.list(game_id=game.game_id) } - power_plants = { - player_id: await PowerPlant.list(player_id=player_id) - for player_id in players.keys() - } - pending_orders = await Order.list(game_id=game.game_id, order_status=OrderStatus.PENDING) user_cancelled_orders = await Order.list(game_id=game.game_id, order_status=OrderStatus.USER_CANCELLED) dataset_row = await DatasetData.get(dataset_id=game.dataset_id, tick=game.current_tick) @@ -91,7 +98,6 @@ async def get_tick_data(self, game: Game) -> TickData: tick_data = TickData( game=game, players=players, - power_plants=power_plants, markets=markets, bots=self.game_data[game.game_id].bots, pending_orders=pending_orders, @@ -123,28 +129,21 @@ def run_markets(self, tick_data: TickData): return tick_data def run_power_plants(self, tick_data: TickData): - for player_id in tick_data.power_plants.keys(): - tick_data.players[player_id].energy = 0 for player_id in tick_data.players.keys(): - power_plants = tick_data.power_plants[player_id] player = tick_data.players[player_id] - for power_plant in power_plants: - type = PowerPlantType(power_plant.type) + player.energy = 0 - if power_plant.has_resources(player): - if not type.is_renewable(): - player[type.name.lower()] -= 1 - else: - power_plant.powered_on = False + for type in PowerPlantType: + to_consume = player[type.name.lower() + "_plants_powered"] - power_plant.temperature = type.get_new_temp( - power_plant.temperature, power_plant.powered_on) + if not type.is_renewable(): + to_consume = min(to_consume, player[type.name.lower()]) + player[type.name.lower()] -= to_consume - if power_plant.temperature > 0.99: - player.energy += power_plant.get_produced_energy( - tick_data.dataset_row) + player.energy += to_consume * type.get_produced_energy( + tick_data.dataset_row) return tick_data @@ -177,10 +176,6 @@ async def save_tick_data(self, tick_data: TickData): for player in tick_data.players.values(): await Player.update(**dataclasses.asdict(player)) - for power_plants in tick_data.power_plants.values(): - for power_plant in power_plants: - await PowerPlant.update(**dataclasses.asdict(power_plant)) - for order in tick_data.updated_orders.values(): await Order.update(**dataclasses.asdict(order)) diff --git a/backend/main.py b/backend/main.py index 3191e60..02d15d0 100644 --- a/backend/main.py +++ b/backend/main.py @@ -11,6 +11,10 @@ import psutil import os from logger import logger +from slowapi import Limiter, _rate_limit_exceeded_handler +from slowapi.util import get_remote_address +from slowapi.middleware import SlowAPIMiddleware +from slowapi.errors import RateLimitExceeded async def background_tasks(): @@ -45,8 +49,25 @@ async def lifespan(app: FastAPI): await database.disconnect() +def team_secret(request: Request): + param = request.query_params.get("team_secret") + if param is None: + return get_remote_address(request) + + return param + + +limiter = Limiter(key_func=team_secret, default_limits=[ + "10/second"], storage_uri="redis://localhost:6379/0") + app = FastAPI(lifespan=lifespan) +app.state.limiter = limiter + +app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) + +app.add_middleware(SlowAPIMiddleware) + @app.exception_handler(Exception) async def validation_exception_handler(request: Request, e: Exception): @@ -63,17 +84,20 @@ async def log_request(message): @app.middleware("http") async def log_request_middleware(request: Request, call_next): + body = await request.body() + start_time = time.time() response = await call_next(request) process_time = (time.time() - start_time) * 1000 formatted_process_time = '{0:.8f}'.format(process_time) - url = f"{request.url.path}" + (f"?{request.query_params}" if request.query_params else "") - body = await request.body() + url = f"{request.url.path}" + \ + (f"?{request.query_params}" if request.query_params else "") + message = (f"{request.client.host}:{request.client.port} - " - f"\"{request.method} {url}\" " - f"[{response.status_code}], " - f"completed in: {formatted_process_time}ms, " - f"request body: {body}") + f"\"{request.method} {url}\" " + f"[{response.status_code}], " + f"completed in: {formatted_process_time}ms, " + f"request body: {body}") asyncio.create_task(log_request(message)) return response diff --git a/backend/model/__init__.py b/backend/model/__init__.py index ecfe32b..7406b66 100644 --- a/backend/model/__init__.py +++ b/backend/model/__init__.py @@ -1,9 +1,7 @@ from .team import Team from .game import Game -from .player import Player +from .player import Player, PowerPlantType from .datasets import Datasets -from .power_plant import PowerPlant -from .power_plant_types import PowerPlantType from .order import Order from .order_types import * from .resource import Resource diff --git a/backend/model/player.py b/backend/model/player.py index 14248d3..a4585b5 100644 --- a/backend/model/player.py +++ b/backend/model/player.py @@ -1,5 +1,41 @@ from dataclasses import dataclass, field from db.table import Table +from enum import Enum +from config import config + + +class PowerPlantType(str, Enum): + COAL = 1 + URANIUM = 2 + BIOMASS = 3 + GAS = 4 + OIL = 5 + GEOTHERMAL = 6 + WIND = 7 + SOLAR = 8 + HYDRO = 9 + + def get_name(self): + return self.name.lower() + + def get_base_price(self): + return config["power_plant"]["base_prices"][self.get_name()] + + def get_warmup_coeff(self): + return config["power_plant"]["warmup_coeff"][self.get_name()] + + def get_cooldown_coeff(self): + return config["power_plant"]["cooldown_coeff"][self.get_name()] + + def get_plant_price(self, power_plant_count: int): + return int(self.get_base_price() * (1 + config["power_plant"]["price_coeff"] * power_plant_count)) + + def get_produced_energy(self, dataset_row: dict): + return dataset_row[self.get_name()] + + def is_renewable(self): + name = self.get_name() + return False if name in ["coal", "uranium", "biomass", "gas", "oil"] else True @dataclass @@ -16,12 +52,33 @@ class Player(Table): money: int = field(default=0) energy: int = field(default=0) + coal: int = field(default=0) uranium: int = field(default=0) biomass: int = field(default=0) gas: int = field(default=0) oil: int = field(default=0) + coal_plants_owned: int = field(default=0) + uranium_plants_owned: int = field(default=0) + biomass_plants_owned: int = field(default=0) + gas_plants_owned: int = field(default=0) + oil_plants_owned: int = field(default=0) + geothermal_plants_owned: int = field(default=0) + wind_plants_owned: int = field(default=0) + solar_plants_owned: int = field(default=0) + hydro_plants_owned: int = field(default=0) + + coal_plants_powered: int = field(default=0) + uranium_plants_powered: int = field(default=0) + biomass_plants_powered: int = field(default=0) + gas_plants_powered: int = field(default=0) + oil_plants_powered: int = field(default=0) + geothermal_plants_powered: int = field(default=0) + wind_plants_powered: int = field(default=0) + solar_plants_powered: int = field(default=0) + hydro_plants_powered: int = field(default=0) + def __getitem__(self, key): return self.__getattribute__(key) diff --git a/backend/model/power_plant.py b/backend/model/power_plant.py deleted file mode 100644 index d526be7..0000000 --- a/backend/model/power_plant.py +++ /dev/null @@ -1,32 +0,0 @@ -from dataclasses import dataclass, field -from db.table import Table -from model.dataset_data import DatasetData -from model.player import Player -from .power_plant_types import PowerPlantType -from .enum_type import enum_type - - -PowerPlantField = enum_type(PowerPlantType) - - -@dataclass -class PowerPlant(Table): - table_name = "power_plants" - power_plant_id: int - type: PowerPlantField - player_id: int - price: int - powered_on: bool = field(default=False) - temperature: float = field(default=0) - - def has_resources(self, player: Player): - type = PowerPlantType(self.type) - - if type.is_renewable(): - return True - - return player[type.get_name().lower()] >= 1 - - def get_produced_energy(self, dataset_row): - type = PowerPlantType(self.type) - return dataset_row[type.get_name().upper()] diff --git a/backend/model/power_plant_types.py b/backend/model/power_plant_types.py deleted file mode 100644 index 75613d5..0000000 --- a/backend/model/power_plant_types.py +++ /dev/null @@ -1,42 +0,0 @@ -from enum import Enum -from config import config - - -class PowerPlantType(Enum): - COAL = 1 - URANIUM = 2 - BIOMASS = 3 - GAS = 4 - OIL = 5 - GEOTHERMAL = 6 - WIND = 7 - SOLAR = 8 - HYDRO = 9 - - def get_name(self): - return self.name.lower() - - def get_base_price(self): - return config["power_plant"]["base_prices"][self.get_name()] - - def get_warmup_coeff(self): - return config["power_plant"]["warmup_coeff"][self.get_name()] - - def get_cooldown_coeff(self): - return config["power_plant"]["cooldown_coeff"][self.get_name()] - - def get_plant_price(self, power_plant_count: int): - return self.get_base_price() + (5000 * power_plant_count) - - def get_new_temp(self, t0, powered_on): - if powered_on: - return cap((1. + t0) * self.get_warmup_coeff() - 1.) - return cap(t0 * self.get_cooldown_coeff()) - - def is_renewable(self): - name = self.get_name() - return False if name in ["coal", "uranium", "biomass", "gas", "oil"] else True - - -def cap(x): - return min(1, max(0, x)) diff --git a/backend/model/test_power_plant.py b/backend/model/test_power_plant.py deleted file mode 100644 index 28f88c3..0000000 --- a/backend/model/test_power_plant.py +++ /dev/null @@ -1,64 +0,0 @@ -import pytest -from model import PowerPlant, Player, PowerPlantType - - -@pytest.fixture -def sample_power_plant(): - return PowerPlant( - power_plant_id=1, - type=PowerPlantType.COAL, - player_id=1, - price=100 - ) - - -@pytest.fixture -def sample_renewable_power_plant(): - return PowerPlant( - power_plant_id=2, - type=PowerPlantType.WIND, - player_id=2, - price=150 - ) - - -@pytest.fixture -def sample_player(): - return Player( - player_id=1, - player_name="John", - game_id=1, - team_id=1, - energy=2, - coal=3, - ) - - -def test_power_plant_initialization(sample_power_plant): - assert sample_power_plant.power_plant_id == 1 - assert sample_power_plant.type == PowerPlantType.COAL - assert sample_power_plant.player_id == 1 - assert sample_power_plant.price == 100 - assert sample_power_plant.powered_on is False - assert sample_power_plant.temperature == 0 - - -def test_power_plant_has_resources(sample_power_plant, sample_renewable_power_plant, sample_player): - assert sample_power_plant.has_resources(sample_player) is True - - # Renewable power plant should always have resources - assert sample_renewable_power_plant.has_resources(sample_player) is True - - # Ensure that player's coal resource count is not enough for non-renewable power plant - sample_player.coal = 0 - assert sample_power_plant.has_resources(sample_player) is False - - -def test_power_plant_get_produced_energy(sample_power_plant, sample_renewable_power_plant, sample_player): - # Test for non-renewable power plant - dataset_row = {'COAL': 50} - assert sample_power_plant.get_produced_energy(dataset_row) == 50 - - # Test for renewable power plant - dataset_row = {'WIND': 30} - assert sample_renewable_power_plant.get_produced_energy(dataset_row) == 30 diff --git a/backend/model/test_power_plant_type.py b/backend/model/test_power_plant_type.py index 9cc94bb..4d02d33 100644 --- a/backend/model/test_power_plant_type.py +++ b/backend/model/test_power_plant_type.py @@ -1,59 +1,39 @@ import pytest -from model import PowerPlantType -from model.power_plant_types import cap +from model.player import PowerPlantType from config import config -@pytest.mark.parametrize("power_plant_type", list(PowerPlantType)) -def test_power_plant_type_config(power_plant_type): - assert power_plant_type.name.lower( - ) in config["power_plant"]["base_prices"], f"Missing price for {power_plant_type.name}" - assert power_plant_type.name.lower( - ) in config["power_plant"]["warmup_coeff"], f"Missing warm-up coefficient for {power_plant_type.name}" - assert power_plant_type.name.lower( - ) in config["power_plant"]["cooldown_coeff"], f"Missing cooldown coefficient for {power_plant_type.name}" +def test_get_name(): + assert PowerPlantType.COAL.get_name() == 'coal' -def test_power_plant_type_methods(): - # Test get_name method - assert PowerPlantType.COAL.get_name() == "coal" - - # Test get_base_price method (assuming the configuration is properly set) +def test_get_base_price(): assert PowerPlantType.COAL.get_base_price( ) == config["power_plant"]["base_prices"]["coal"] - # Test get_warmup_coeff method + +def test_get_warmup_coeff(): assert PowerPlantType.COAL.get_warmup_coeff( ) == config["power_plant"]["warmup_coeff"]["coal"] - # Test get_cooldown_coeff method + +def test_get_cooldown_coeff(): assert PowerPlantType.COAL.get_cooldown_coeff( ) == config["power_plant"]["cooldown_coeff"]["coal"] - # Test get_plant_price method - # Assuming power_plant_count is 2 for testing - assert PowerPlantType.COAL.get_plant_price( - 2) == config["power_plant"]["base_prices"]["coal"] + 10000 - - # Test get_new_temp method - # Assuming t0 is 0.5 and powered_on is True for testing - assert PowerPlantType.COAL.get_new_temp(0.5, True) == cap( - (1. + 0.5) * config["power_plant"]["warmup_coeff"]["coal"] - 1.) - # Test get_new_temp method when powered_on is False - # Assuming t0 is 0.5 for testing - assert PowerPlantType.COAL.get_new_temp(0.5, False) == cap( - 0.5 * config["power_plant"]["cooldown_coeff"]["coal"]) +def test_get_plant_price(): + assert PowerPlantType.COAL.get_plant_price( + 2) == config["power_plant"]["base_prices"]["coal"] * (1 + config["power_plant"]["price_coeff"] * 2) - # Test is_renewable method for COAL (non-renewable) - assert PowerPlantType.COAL.is_renewable() is False - # Test is_renewable method for WIND (renewable) - assert PowerPlantType.WIND.is_renewable() is True +def test_get_produced_energy(): + # Mocking dataset_row + dataset_row = {'coal': 10, 'uranium': 20, 'biomass': 30} + assert PowerPlantType.COAL.get_produced_energy( + dataset_row) == dataset_row["coal"] -def test_cap_function(): - # Test cap function for various input values - assert cap(0.5) == 0.5 - assert cap(1.5) == 1 - assert cap(-0.5) == 0 +def test_is_renewable(): + assert PowerPlantType.COAL.is_renewable() == False + assert PowerPlantType.SOLAR.is_renewable() == True diff --git a/backend/requirements.txt b/backend/requirements.txt index b8f4ea2..915c351 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -10,4 +10,6 @@ uvicorn==0.26.0 xheap==0.17 asyncpg==0.29.0 uvicorn==0.26.0 -coverage==7.4.0 \ No newline at end of file +coverage==7.4.0 +coredis==4.16.0 +redis==5.0.1 \ No newline at end of file diff --git a/backend/routers/users/dependencies.py b/backend/routers/users/dependencies.py index 456718e..d76ad76 100644 --- a/backend/routers/users/dependencies.py +++ b/backend/routers/users/dependencies.py @@ -1,5 +1,5 @@ from fastapi import HTTPException, Query, Depends -from model import Team, Player, PowerPlant, Game +from model import Team, Player, Game async def game_id(game_id: int) -> int: @@ -31,13 +31,3 @@ async def player(player_id: int, game_id: int = Depends(game_id), team_id: int = if player.is_active == False: raise HTTPException(400, f"This player is inactive") return player - - -async def power_plant(power_plant_id: int, player: Player = Depends(player)) -> PowerPlant: - try: - power_plant = await PowerPlant.get(power_plant_id=power_plant_id) - except: - raise HTTPException(status_code=403, detail="Invalid power_plant_id") - if power_plant.player_id != player.player_id: - raise HTTPException(400, "This power plant doesn't belong to you") - return power_plant diff --git a/backend/routers/users/power_plant.py b/backend/routers/users/power_plant.py index 12f62a0..5c9f029 100644 --- a/backend/routers/users/power_plant.py +++ b/backend/routers/users/power_plant.py @@ -2,8 +2,8 @@ from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel from db import database -from model import Player, PowerPlant, PowerPlantType -from .dependencies import player, power_plant +from model import Player, PowerPlantType +from .dependencies import player from config import config # POWER_PLANT PATHS @@ -21,82 +21,66 @@ router = APIRouter(dependencies=[Depends(player)]) -@router.get("/game/{game_id}/player/{player_id}/plant/prices") -async def list_plant_prices(player_id: int): - return [ - {"type": type, - "name": type.name, - "price": type.get_plant_price(player_id=player_id)} - for type in PowerPlantType - ] - - @router.get("/game/{game_id}/player/{player_id}/plant/list") async def list_plants(player_id: int): - return [{**dataclasses.asdict(x), - "sell_price": round(x.price * config["power_plant_sell_coef"])} - for x in (await PowerPlant.list(player_id=player_id))] + player = await Player.get(player_id=player_id) + return { + x.name: { + "plants_powered": player[x.name.lower() + "_plants_powered"], + "plants_owned": player[x.name.lower() + "_plants_owned"], + "next_price": x.get_plant_price(player[x.name.lower() + "_plants_owned"]), + "sell_price": round(x.get_plant_price(player[x.name.lower() + "_plants_owned"]) * config["power_plant"]["sell_coeff"]), + } + for x in PowerPlantType + } -class PlantBuy(BaseModel): + +class PlantBuySell(BaseModel): type: PowerPlantType @router.post("/game/{game_id}/player/{player_id}/plant/buy") -async def buy_plant(player_id: int, plant: PlantBuy): +async def buy_plant(player_id: int, plant: PlantBuySell): type = PowerPlantType(plant.type) async with database.transaction(): - player_balance = (await Player.get(player_id=player_id)).money - price = type.get_plant_price(player_id=player_id) + player = await Player.get(player_id=player_id) + plant_count = player[type.name.lower() + "_plants_owned"] + plant_price = type.get_plant_price(plant_count) - if player_balance < price: + if player.money < plant_price: raise HTTPException(status_code=400, detail="Not enough money") - await Player.update(player_id=player_id, money=player_balance - price) - - plant_id = await PowerPlant.create(type=type.value, player_id=player_id, price=price) - - return {"power_plant_id": plant_id} - - -@router.get("/game/{game_id}/player/{player_id}/plant/{power_plant_id}/sell_price") -async def get_sell_price(power_plant: PowerPlant = Depends(power_plant)): - sell_coef = config["power_plant_sell_coef"] + await Player.update(player_id=player_id, money=player.money - plant_price) + await Player.update(player_id=player_id, **{type.name.lower() + "_plants_owned": plant_count + 1}) - return {"sell_price": round(power_plant.price * sell_coef)} - -@router.get("/game/{game_id}/player/{player_id}/plant/{power_plant_id}/sell") -async def sell_plant(power_plant: PowerPlant = Depends(power_plant)): - - sell_coef = config["power_plant_sell_coef"] +@router.post("/game/{game_id}/player/{player_id}/plant/sell") +async def sell_plant(player_id: int, plant: PlantBuySell): + type = PowerPlantType(plant.type) async with database.transaction(): - sell_price = round(power_plant.price * sell_coef) + player = await Player.get(player_id=player_id) + plant_count = player[type.name.lower() + "_plants_owned"] + plant_price = type.get_plant_price(plant_count) - balance = (await Player.get(player_id=power_plant.player_id)).money + await Player.update(player_id=player_id, money=player.money + round(plant_price * config["power_plant"]["sell_coeff"])) + await Player.update(player_id=player_id, **{type.name.lower() + "_plants_owned": plant_count - 1}) - await Player.update(player_id=power_plant.player_id, money=balance + sell_price) - await PowerPlant.delete(power_plant_id=power_plant.power_plant_id) - return {"sell_price": sell_price, "new_balance": balance + sell_price} - - -@router.get("/game/{game_id}/player/{player_id}/plant/{power_plant_id}/on") -async def turn_on(power_plant: PowerPlant = Depends(power_plant)): - async with database.transaction(): - await PowerPlant.update(power_plant_id=power_plant.power_plant_id, is_on=True) - return {"success": True} +class PowerOn(BaseModel): + number: int -@router.get("/game/{game_id}/player/{player_id}/plant/{power_plant_id}/off") -async def turn_off(power_plant: PowerPlant = Depends(power_plant)): +@router.post("/game/{game_id}/player/{player_id}/plant/on") +async def turn_on(player_id: int, plant: PowerOn): async with database.transaction(): - await PowerPlant.update(power_plant_id=power_plant.power_plant_id, is_on=False) - return {"success": True} + player = await Player.get(player_id=player_id) + plant_count = player[type.name.lower() + "_plants_owned"] + if plant_count < plant.number or plant.number < 0: + raise HTTPException( + status_code=400, detail="Not enough plants or invalid number") -@router.get("/game/{game_id}/player/{player_id}/plant/{power_plant_id}") -async def get_plant(power_plant: PowerPlant = Depends(power_plant)): - return await PowerPlant.get(power_plant_id=power_plant.power_plant_id) + await Player.update(player_id=player_id, **{type.name.lower() + "_plants_powered": plant.number}) diff --git a/backend/run_migrations.py b/backend/run_migrations.py index 453a812..9ba41d1 100644 --- a/backend/run_migrations.py +++ b/backend/run_migrations.py @@ -5,6 +5,7 @@ async def main(): await database.connect() + if config['testing']: await migration.drop_tables() await migration.run_migrations() @@ -16,8 +17,8 @@ async def main(): logger.warn("Migration script failed, dropping tables...") await migration.drop_tables() await migration.run_migrations() - + if __name__ == "__main__": import asyncio - asyncio.run(main()) \ No newline at end of file + asyncio.run(main()) diff --git a/dataset/index.html b/dataset/index.html deleted file mode 100644 index 6c0fb5a..0000000 --- a/dataset/index.html +++ /dev/null @@ -1,116 +0,0 @@ - - -
-