moon-miner/mm.py

519 lines
15 KiB
Python

from dateutil.parser import parse
from dataclasses import dataclass
import sqlite3 as sql
import PySimpleGUI as sg
from ImguiWindow import ImguiWindow, load_texture
import glfw
import imgui as im
import random
import datetime
def get_bank():
cursor = conn.cursor()
cursor.execute("""
SELECT resource_account.id,resource.name,balance FROM resource_account
INNER JOIN resource ON resource.id = resource_account.resource_id
WHERE user_id = ?
""", (world.current_user_id,))
resources = {id:(name, balance) for id,name,balance in cursor.fetchall()}
cursor.execute("SELECT bank_account.id,balance FROM bank_account WHERE user_id = ?",
(world.current_user_id,))
bank = cursor.fetchone()
cursor.close()
return (bank, resources)
def get_store_items():
cursor = conn.cursor()
cursor.execute("""
SELECT store_item.id, store_item.name, price, claim_amount, completion_time_mins
FROM store_item
""")
items = {item[0]:item[1:] for item in cursor.fetchall()}
cursor.close()
return items
def get_stakes():
cursor = conn.cursor()
cursor.execute("""
SELECT staking_event.id, well_id, inventory_item_id, stake_amount, duration_in_mins,
staking_event.created_at,
CASE WHEN claim_event.staking_event_id IS NULL THEN 1 ELSE 0 END AS is_active
FROM staking_event
INNER JOIN resource_well ON resource_well.id = staking_event.well_id
INNER JOIN staking_source ON staking_source.id = resource_well.source_id
LEFT JOIN claim_event ON claim_event.staking_event_id = staking_event.id
WHERE staking_source.user_id = ?;
""", (world.current_user_id,))
stakes = {item[0]:(item[1],item[2],item[3],item[4],bool(item[-1])) for item in cursor.fetchall()}
cursor.close()
return stakes
# An active stake is basically defined as one that has yet to be claimed
def get_active_stakes(source_id):
cursor = conn.cursor()
cursor.execute("""
SELECT staking_event.id, well_id, inventory_item_id, stake_amount, duration_in_mins,
staking_event.created_at
FROM staking_event
LEFT JOIN claim_event ON staking_event.id = claim_event.staking_event_id
INNER JOIN resource_well ON resource_well.id = staking_event.well_id
WHERE resource_well.source_id = ? AND claim_event.staking_event_id IS NULL;
""", (source_id,))
active_stakes = {item[0]:item[1:] for item in cursor.fetchall()}
cursor.close()
return active_stakes
def get_wells(source_id):
cursor = conn.cursor()
cursor.execute("""
SELECT resource_well.id,name,supply,staking_event.created_at
FROM resource_well
INNER JOIN resource ON resource.id = resource_well.resource_id
LEFT JOIN staking_event ON staking_event.well_id = resource_well.id
WHERE resource_well.source_id = ?;
""", (source_id,))
wells = {id: (name,supply,timestamp) for id,name,supply,timestamp in cursor.fetchall()}
cursor.close()
return wells
def get_all_wells():
cursor = conn.cursor()
cursor.execute("""
SELECT resource_well.id,name,supply FROM resource_well
INNER JOIN resource ON resource.id = resource_well.resource_id
INNER JOIN staking_source ON staking_source.id = resource_well.source_id
WHERE staking_source.user_id = ?;
""", (world.current_user_id,))
wells = {w[0]:w[1:] for w in cursor.fetchall()}
cursor.close()
return wells
def get_moons():
cursor = conn.cursor()
cursor.execute("SELECT id,created_at FROM staking_source WHERE user_id = ?",
(world.current_user_id,))
staking_sources = {}
for sid,ts in cursor.fetchall():
staking_sources[sid] = (ts, get_wells(sid), get_active_stakes(sid))
cursor.close()
return staking_sources
def get_inventory():
cursor = conn.cursor()
cursor.execute("""
SELECT inventory_item.id,store_item_id, COUNT(upgrade_event.id) as upgrades
FROM inventory_item
LEFT JOIN upgrade_event ON inventory_item.id = upgrade_event.inventory_item_id
WHERE inventory_item.user_id = ?
GROUP BY inventory_item.id;
""", (world.current_user_id,))
inventory = {item[0]:item[1:] for item in cursor.fetchall()}
cursor.close()
return inventory
def mint():
cursor = conn.cursor()
rand_hash = "%010x" % random.randrange(16 ** 16)
cursor.execute('BEGIN')
source_id = -1
try:
cursor.execute("""
INSERT INTO staking_source (user_id, address) VALUES (?, ?)
""", (world.current_user_id, f"0x{rand_hash}"))
source_id = cursor.lastrowid
for id,_ in world.resources:
init_supply = random.randint(50, 200)
cursor.execute("""
INSERT INTO resource_well (source_id, resource_id, supply) VALUES (?, ?, ?)
""", (source_id, id, init_supply))
conn.commit()
except sql.Error as error:
print(error)
conn.rollback()
finally:
cursor.close()
world.staking_sources = get_moons()
return source_id
def buy_item(item_id):
cursor = conn.cursor()
cost = world.store[item_id][1]
if cost > world.bank[0][1]:
print(f"Not enough money: Bank {world.bank[0][1]} < Cost {cost}")
return -1
cursor.execute('BEGIN')
try:
cursor.execute("""
INSERT INTO inventory_item (user_id, store_item_id) VALUES (?, ?)
""", (world.current_user_id, item_id))
cursor.execute("""
UPDATE bank_account SET balance = balance - ? WHERE bank_account.id = ?
""", (cost, world.bank[0][0]))
except sql.Error as error:
conn.rollback()
finally:
item_id = cursor.lastrowid
conn.commit()
cursor.close()
world.inventory = get_inventory()
world.bank = get_bank()
return item_id
def sell_item(item_id):
cursor = conn.cursor()
cost = world.store[world.inventory[item_id][0]][1]
cursor.execute('BEGIN')
try:
# TODO: We have to cascade delete stakes and claims after this
cursor.execute("DELETE FROM inventory_item WHERE user_id = ? AND id = ?",
(world.current_user_id, item_id))
cursor.execute("""
UPDATE bank_account SET balance = balance + ? WHERE bank_account.id = ?
""", (cost, world.bank[0][0]))
except sql.Error as error:
conn.rollback()
finally:
conn.commit()
cursor.close()
world.inventory = get_inventory()
world.bank = get_bank()
def mine(well_id, item_id):
cursor = conn.cursor()
store_item = world.store[world.inventory[item_id][0]]
cursor.execute("""
INSERT INTO staking_event (well_id, inventory_item_id, stake_amount, duration_in_mins)
VALUES (?, ?, ?, ?)
""", (well_id, item_id, store_item[2], store_item[3]))
conn.commit()
world.staking_sources = get_moons()
world.stakes = get_stakes()
cursor.close()
def claim(staking_event_id):
cursor = conn.cursor()
claim_amount = world.stakes[staking_event_id][2]
well_id = world.stakes[staking_event_id][0]
try:
cursor.execute("SELECT supply FROM resource_well WHERE id = ?",
(well_id,))
supply = cursor.fetchone()[0]
claim_amount = claim_amount if supply >= claim_amount else supply
print(claim_amount)
cursor.execute("""
UPDATE resource_well SET supply = supply - ? WHERE resource_well.id = ?
""", (claim_amount, well_id))
cursor.execute("INSERT INTO claim_event (claim_amount, staking_event_id) VALUES (?, ?)",
(claim_amount, staking_event_id))
cursor.execute("""
WITH w_res_id AS (SELECT resource_id FROM resource_well WHERE id = ?)
UPDATE resource_account SET balance = balance + ?
FROM w_res_id
WHERE resource_account.user_id = ?
AND resource_account.resource_id = w_res_id.resource_id
""", (well_id, claim_amount, world.current_user_id))
except sql.Error as error:
print(error)
conn.rollback()
finally:
conn.commit()
world.stakes = get_stakes()
world.staking_sources = get_moons()
world.bank = get_bank()
cursor.close()
def upgrade(item_id):
cursor = conn.cursor()
cursor.execute("INSERT INTO upgrade_event (inventory_item_id) VALUES (?)", (item_id,))
conn.commit()
world.inventory = get_inventory()
cursor.close()
def destroy(source_id):
cursor = conn.cursor()
cursor.execute("DELETE FROM staking_source WHERE id = ?", (source_id,))
conn.commit()
cursor.close()
world.staking_sources = get_moons()
def sell_all():
cursor = conn.cursor()
cursor.execute("DELETE FROM inventory_item WHERE user_id = ?", (current_user_id,))
conn.commit()
cursor.close()
world.inventory = get_inventory()
def trade():
cursor = conn.cursor()
cursor.execute("""
UPDATE resource_account SET balance = balance - ? WHERE resource_account.user_id = ?
""", (10, world.current_user_id))
cursor.execute("""
UPDATE bank_account SET balance = balance + ? WHERE bank_account.user_id = ?
""", (1, world.current_user_id))
conn.commit()
cursor.close()
world.bank = get_bank()
def draw_dashboard():
im.text(f"Current User: {world.current_user}")
for _ in range(5):
im.spacing()
im.text(f"Moon Bucks: {world.bank[0][1]}")
for _ in range(10):
im.spacing()
for id,(name,balance) in world.bank[1].items():
im.text(f"{name.capitalize()}: {balance}")
for _ in range(10):
im.spacing()
resources = world.bank[1]
can_sell = True
for _,supply in resources.values():
if supply < 10:
can_sell = False
im.text("Sell 10 resources for 1 Moon Bucks")
if can_sell:
if im.button("Sell"):
trade()
else:
im.text("Sell (Not enough funds)")
def draw_store():
for id,(name,price,claim,duration) in world.store.items():
owned = False
for (store_item_id,_) in world.inventory.values():
if id == store_item_id:
owned = True
im.text(f"{name}: Mine {claim} in {duration} mins")
if owned:
im.text_disabled(f"Buy {price}")
else:
im.push_id(f"Buy{id}")
if im.button(f"Buy {price}"):
buy_item(id)
im.pop_id()
for _ in range(5):
im.spacing()
def draw_inventory():
for id,(sid,tier) in world.inventory.items():
im.text(f"{world.store[sid][0]} - Tier {tier+1}")
im.push_id(f"Upgrade{id}")
if im.button("Upgrade"):
upgrade(id)
im.pop_id()
im.same_line()
im.push_id(f"Sell{id}")
if im.button("Sell"):
sell_item(id)
im.pop_id()
for _ in range(5):
im.spacing()
def draw_moons():
if im.button("Mint"):
mint()
im.begin_child("Moons")
im.columns(3, 'fileLlist')
im.set_column_width(0, 250)
im.set_column_width(1, 600)
im.separator()
im.text("Moon")
im.next_column()
im.text("Resources")
im.next_column()
im.text("Active Stakes")
im.next_column()
im.separator()
for source_id,(ts,wells,stakes) in world.staking_sources.items():
im.image(world.moon_img_tex_id, 240, 200)
im.push_id(f"Destroy{id}")
if im.button("Destroy"):
destroy(source_id)
im.pop_id()
im.next_column()
for well_id,(name,supply,ts) in wells.items():
im.text(f"{name.capitalize()}: {supply}")
for item_id,(store_id,_) in world.inventory.items():
item_name = world.store[store_id][0]
skip = False
for ss in world.stakes.values():
if ss[1] == item_id and ss[4] == True or supply == 0:
skip = True
if skip == True:
continue
im.push_id(f"Mine{well_id}")
if im.button(item_name):
mine(well_id, item_id)
im.pop_id()
im.same_line()
im.spacing()
im.next_column()
for stake_id,(well_id,invId,amount,dur,start) in stakes.items():
start_time = parse(start)
now = datetime.datetime.utcnow().replace(microsecond=0)
elapsed = now - start_time
remaining = start_time + datetime.timedelta(minutes=dur) - now
btn_txt = f"Claim {amount} {world.wells[well_id][0].capitalize()}"
if elapsed > datetime.timedelta(minutes=dur):
im.push_id(f"Claim{stake_id}")
if im.button(btn_txt):
claim(stake_id)
im.pop_id()
im.text(f"Finished {elapsed} ago")
else:
im.text_disabled(btn_txt)
im.text(f"{remaining} left")
im.next_column()
im.separator()
im.columns(1)
im.end_child()
def draw_panels():
screen_width, screen_height = glfw.get_video_mode(glfw.get_primary_monitor())[0]
# TODO: We probably don't need to create a "Main" window...
im.set_next_window_size(screen_width, screen_height)
# Set the next window position to (0, 0) to make the window cover the entire screen
im.set_next_window_position(0, 0)
f = im.WINDOW_NO_TITLE_BAR | im.WINDOW_NO_RESIZE | im.WINDOW_NO_MOVE | im.WINDOW_NO_COLLAPSE
im.begin("Main", flags=f)
im.end()
# Dashboard
im.set_next_window_position(0, 0)
im.set_next_window_size(400, 450)
f = im.WINDOW_NO_RESIZE | im.WINDOW_NO_MOVE
im.begin("Dashboard", flags=f)
draw_dashboard()
im.end()
# Store
im.set_next_window_position(402, 0)
im.set_next_window_size(500, 450)
f = im.WINDOW_NO_RESIZE | im.WINDOW_NO_MOVE
im.begin("Store", flags=f)
draw_store()
im.end()
# Inventory
im.set_next_window_position(902, 0)
im.set_next_window_size(500, 450)
f = im.WINDOW_NO_RESIZE | im.WINDOW_NO_MOVE
im.begin("Inventory", flags=f)
draw_inventory()
im.end()
# Moons
im.set_next_window_position(0, 452)
im.set_next_window_size(1400, 540)
f = im.WINDOW_NO_RESIZE | im.WINDOW_NO_MOVE | im.WINDOW_ALWAYS_VERTICAL_SCROLLBAR
im.begin("Moons", flags=f)
draw_moons()
im.end()
@dataclass
class World:
current_user = "Joe"
current_user_id = 1
moon_img_tex_id = -1
bank = dict()
resources = []
id_to_resource = dict()
resource_to_id = dict()
store = dict()
inventory = dict()
staking_sources = dict()
stakes = dict()
world = World()
conn = sql.connect("mm.db", check_same_thread=False)
conn.execute('PRAGMA foreign_keys = ON')
world.current_user = "Joe"
world.current_user_id = 1
cursor = conn.cursor()
cursor.execute("SELECT id,name FROM resource")
world.resources = cursor.fetchall()
cursor.close()
world.id_to_resource = {id: name for id, name in world.resources}
world.resource_to_id = {name: id for id, name in world.resources}
world.bank = get_bank()
world.store = get_store_items()
world.inventory = get_inventory()
world.staking_sources = get_moons()
world.stakes = get_stakes()
world.wells = get_all_wells()
def imgui_init():
world.moon_img_tex_id = texture_id = load_texture("moon.png")
imguiWindow = ImguiWindow(imgui_init, draw_panels, "Moon Miner Test", 1400, 1000)
conn.close()