model.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573
  1. import re
  2. import sqlite3 as db
  3. import sys
  4. import uuid
  5. import db_setup
  6. from game import CURRENCY_NAME
  7. from util import debug, random_chars
  8. connection: db.Connection = None
  9. cursor: db.Cursor = None
  10. db_name = None
  11. def query_save_name():
  12. global db_name
  13. if debug:
  14. db_name = 'test.db'
  15. return
  16. while True:
  17. save_name = input('Name of the savegame: ')
  18. if re.match(r"[A-Za-z0-9.-]{0,50}", save_name):
  19. db_name = save_name + '.db'
  20. return
  21. else:
  22. print('Must match "[A-Za-z0-9.-]{0,50}"')
  23. def connect(reconnect=False):
  24. global connection
  25. global cursor
  26. global db_name
  27. if reconnect:
  28. connection.commit()
  29. connection.close()
  30. cursor = None
  31. connection = None
  32. db_name = None
  33. if connection is None or cursor is None:
  34. query_save_name()
  35. try:
  36. connection = db.connect(db_name)
  37. cursor = connection.cursor()
  38. except db.Error as e:
  39. print("Database error %s:" % e.args[0])
  40. sys.exit(1)
  41. # finally:
  42. # if con is not None:
  43. # con.close()
  44. def setup():
  45. connect()
  46. db_setup.setup(cursor)
  47. connection.commit()
  48. def login(username, password):
  49. connect()
  50. # do not allow login as bank
  51. if password == '':
  52. return None
  53. cursor.execute('''
  54. SELECT rowid
  55. FROM users
  56. WHERE username = ?
  57. AND password = ?
  58. ''', (username, password))
  59. user_id = cursor.fetchone()
  60. if user_id:
  61. return new_session(user_id)
  62. else:
  63. return None
  64. def register(username, password, game_key):
  65. connect()
  66. if username == '':
  67. return False
  68. if password == '':
  69. return False
  70. cursor.execute('''
  71. INSERT INTO users
  72. (username, password)
  73. VALUES (? , ?)
  74. ''', (username, password))
  75. own(get_user_id_by_name(username), CURRENCY_NAME)
  76. if game_key != '':
  77. if valid_key(game_key):
  78. activate_key(game_key, get_user_id_by_name(username))
  79. return True
  80. def own(user_id, ownable_name):
  81. cursor.execute('''
  82. WITH one_ownable_id AS (SELECT rowid FROM ownables WHERE name = ?),
  83. one_user_id AS (SELECT ?)
  84. INSERT INTO ownership (user_id, ownable_id)
  85. SELECT *
  86. FROM one_user_id, one_ownable_id
  87. WHERE NOT EXISTS (
  88. SELECT * FROM ownership
  89. WHERE ownership.user_id IN one_user_id
  90. AND ownership.ownable_id IN one_ownable_id
  91. )
  92. ''', (ownable_name, user_id,))
  93. def send_ownable(from_user_id, to_user_id, ownable_name, amount):
  94. connect()
  95. if amount < 0:
  96. return False
  97. if from_user_id != bank_id():
  98. cursor.execute('''
  99. UPDATE ownership
  100. SET amount = amount - ?
  101. WHERE user_id = ?
  102. AND ownable_id = (SELECT rowid FROM ownables WHERE name = ?)
  103. ''', (amount, from_user_id, ownable_name,))
  104. if not cursor.fetchone():
  105. return False
  106. cursor.execute('''
  107. UPDATE ownership
  108. SET amount = amount + ?
  109. WHERE user_id = ?
  110. AND ownable_id = (SELECT rowid FROM ownables WHERE name = ?)
  111. ''', (amount, to_user_id, ownable_name))
  112. if cursor.rowcount == 0:
  113. return False
  114. return True
  115. def valid_key(key):
  116. connect()
  117. cursor.execute('''
  118. SELECT key
  119. FROM keys
  120. WHERE used_by_user_id IS NULL
  121. AND key = ?
  122. ''', (key,))
  123. if cursor.fetchone():
  124. return True
  125. else:
  126. return False
  127. def new_session(user_id):
  128. connect()
  129. session_id = str(uuid.uuid4())
  130. cursor.execute('''
  131. INSERT INTO SESSIONS
  132. (user_id, session_id)
  133. VALUES (? , ?)
  134. ''', (user_id[0], session_id))
  135. return session_id
  136. def save_key(key):
  137. connect()
  138. cursor.execute('''
  139. INSERT INTO keys
  140. (key)
  141. VALUES (?)
  142. ''', (key,))
  143. def drop_old_sessions():
  144. connect()
  145. cursor.execute('''
  146. DELETE FROM sessions s1
  147. WHERE
  148. (SELECT COUNT(*) as newer
  149. FROM sessions s2
  150. WHERE s1.user_id = s2.user_id
  151. AND s1.rowid < s2.rowid) >= 10
  152. ''')
  153. def user_exists(username):
  154. connect()
  155. cursor.execute('''
  156. SELECT rowid
  157. FROM users
  158. WHERE username = ?
  159. ''', (username,))
  160. if cursor.fetchone():
  161. return True
  162. else:
  163. return False
  164. def unused_keys():
  165. connect()
  166. cursor.execute('''
  167. SELECT key
  168. FROM keys
  169. WHERE used_by_user_id IS NULL
  170. ''')
  171. return [str(key[0]).strip().upper() for key in cursor.fetchall()]
  172. def get_user_id_by_session_id(session_id):
  173. connect()
  174. cursor.execute('''
  175. SELECT users.rowid
  176. FROM sessions, users
  177. WHERE sessions.session_id = ?
  178. AND users.rowid = sessions.user_id
  179. ''', (session_id,))
  180. ids = cursor.fetchone()
  181. if ids is None:
  182. return False
  183. return ids[0]
  184. def get_user_id_by_name(username):
  185. connect()
  186. cursor.execute('''
  187. SELECT users.rowid
  188. FROM users
  189. WHERE username = ?
  190. ''', (username,))
  191. return cursor.fetchone()[0]
  192. def get_user_ownership(user_id):
  193. connect()
  194. cursor.execute('''
  195. SELECT ownables.name, ownership.amount
  196. FROM ownership, ownables
  197. WHERE user_id = ?
  198. AND ownership.ownable_id = ownables.rowid
  199. ''', (user_id,))
  200. return cursor.fetchall()
  201. def activate_key(key, user_id):
  202. connect()
  203. cursor.execute('''
  204. UPDATE keys
  205. SET used_by_user_id = ?
  206. WHERE used_by_user_id IS NULL
  207. AND key = ?
  208. ''', (user_id, key,))
  209. if cursor.rowcount == 0:
  210. raise AssertionError
  211. send_ownable(bank_id(), user_id, CURRENCY_NAME, 1000)
  212. def bank_id():
  213. connect()
  214. cursor.execute('''
  215. SELECT users.rowid
  216. FROM users
  217. WHERE username = 'bank'
  218. ''')
  219. return cursor.fetchone()[0]
  220. def valid_session_id(session_id):
  221. connect()
  222. cursor.execute('''
  223. SELECT rowid
  224. FROM sessions
  225. WHERE session_id = ?
  226. ''', (session_id,))
  227. if cursor.fetchone():
  228. return True
  229. else:
  230. return False
  231. def get_user_orders(user_id):
  232. connect()
  233. cursor.execute('''
  234. SELECT
  235. CASE
  236. WHEN orders.buy THEN 'Buy'
  237. ELSE 'Sell'
  238. END,
  239. ownables.name,
  240. orders.ordered_amount - orders.executed_amount,
  241. orders."limit",
  242. CASE
  243. WHEN orders."limit" IS NULL THEN NULL
  244. WHEN orders.stop_loss THEN 'Yes'
  245. ELSE 'No'
  246. END,
  247. orders.ordered_amount
  248. FROM orders, ownables, ownership
  249. WHERE ownership.user_id = ?
  250. AND ownership.ownable_id = ownables.rowid
  251. AND orders.ownership_id = ownership.rowid
  252. ORDER BY orders.buy DESC, ownables.name ASC
  253. ''', (user_id,))
  254. return cursor.fetchall()
  255. def sell_ordered_amount(user_id, ownable_id):
  256. connect()
  257. # if ownable_id == currency_id():
  258. # return 0
  259. cursor.execute('''
  260. SELECT SUM(orders.ordered_amount - orders.executed_amount)
  261. FROM orders, ownership
  262. WHERE ownership.rowid = orders.ownership_id
  263. AND ownership.user_id = ?
  264. AND ownership.ownable_id = ?
  265. AND NOT orders.buy
  266. ''', (user_id, ownable_id))
  267. return cursor.fetchone()[0]
  268. def user_owns_at_least(amount, user_id, ownable_id):
  269. connect()
  270. cursor.execute('''
  271. SELECT rowid
  272. FROM ownership
  273. WHERE user_id = ?
  274. AND ownable_id = ?
  275. AND amount - ? >= ?
  276. ''', (user_id, ownable_id, sell_ordered_amount(user_id, ownable_id), amount))
  277. if cursor.fetchone():
  278. return True
  279. else:
  280. return False
  281. def news():
  282. connect()
  283. cursor.execute('''
  284. SELECT *
  285. FROM news
  286. ORDER BY dt
  287. LIMIT 20
  288. ''')
  289. return cursor.fetchall()
  290. def ownable_name_exists(name):
  291. connect()
  292. cursor.execute('''
  293. SELECT rowid
  294. FROM ownables
  295. WHERE name = ?
  296. ''', (name,))
  297. if cursor.fetchone():
  298. return True
  299. else:
  300. return False
  301. def new_stock(name=None):
  302. connect()
  303. while name is None:
  304. name = random_chars(6)
  305. if ownable_name_exists(name):
  306. name = None
  307. cursor.execute('''
  308. INSERT INTO ownables(name)
  309. VALUES (?)
  310. ''', (name,))
  311. return name
  312. def new_stocks(count=1):
  313. return [new_stock() for _ in range(count)]
  314. def ownable_id_by_name(ownable_name):
  315. connect()
  316. cursor.execute('''
  317. SELECT rowid
  318. FROM ownables
  319. WHERE name = ?
  320. ''', (ownable_name,))
  321. return cursor.fetchone()[0]
  322. def get_ownership_id(ownable_id, user_id):
  323. connect()
  324. cursor.execute('''
  325. SELECT rowid
  326. FROM ownership
  327. WHERE ownable_id = ?
  328. AND user_id = ?
  329. ''', (ownable_id, user_id,))
  330. return cursor.fetchone()[0]
  331. def currency_id():
  332. connect()
  333. cursor.execute('''
  334. SELECT rowid
  335. FROM ownables
  336. WHERE name = ?
  337. ''', (CURRENCY_NAME,))
  338. return cursor.fetchone()[0]
  339. def execute_orders(ownable_id):
  340. connect()
  341. executed_any = True
  342. while executed_any:
  343. executed_any = False
  344. # find order to execute
  345. cursor.execute('''
  346. SELECT buy_order.*, sell_order.*, buyer.*, seller.*
  347. FROM orders buy_order, orders sell_order, ownership buyer, ownership seller
  348. WHERE buy_order.buy AND NOT sell_order.buy
  349. AND buyer.rowid = buy_order.ownership_id
  350. AND seller.rowid = sell_order.ownership_id
  351. AND buyer.ownable_id = ?
  352. AND seller.ownable_id = ?
  353. AND (buy_order."limit" IS NULL
  354. OR sell_order."limit" IS NULL
  355. OR (sell_order."limit" < buy_order."limit"
  356. AND NOT sell_order.stop_loss
  357. AND NOT buy_order.stop_loss))
  358. ORDER BY COALESCE(sell_order."limit", 0) ASC,
  359. COALESCE(buy_order."limit", 0) DESC
  360. LIMIT 1
  361. ''', (ownable_id, ownable_id,))
  362. matching_orders = cursor.fetchone()
  363. if not matching_orders:
  364. continue
  365. # TODO compute execution price, amount, buyer_id and seller_id from matching_orders
  366. price = -1
  367. if price < 0 or amount < 0:
  368. return AssertionError()
  369. # actually execute the order
  370. cursor.execute('''
  371. UPDATE ownership
  372. SET amount = amount - ?
  373. WHERE user_id = ?
  374. ''', (price, buyer_id))
  375. if not cursor.fetchone():
  376. raise AssertionError()
  377. cursor.execute('''
  378. UPDATE ownership
  379. SET amount = amount - ?
  380. WHERE user_id = ?
  381. ''', (amount, seller_id))
  382. if not cursor.fetchone():
  383. raise AssertionError()
  384. cursor.execute('''
  385. UPDATE ownership
  386. SET amount = amount + ?
  387. WHERE user_id = ?
  388. ''', (amount, buyer_id))
  389. if not cursor.fetchone():
  390. raise AssertionError()
  391. cursor.execute('''
  392. UPDATE ownership
  393. SET amount = amount + ?
  394. WHERE user_id = ?
  395. ''', (price, seller_id))
  396. if not cursor.fetchone():
  397. raise AssertionError()
  398. cursor.execute('''
  399. UPDATE orders
  400. SET executed_amount = executed_amount + ?
  401. WHERE rowid = ?
  402. OR rowid = ?
  403. ''', (amount, buy_order_id, sell_order_id))
  404. if not cursor.fetchone():
  405. raise AssertionError()
  406. executed_any = True
  407. if seller_id != buyer_id: # prevent showing self-transactions to keep the price reasonable
  408. cursor.execute('''
  409. INSERT INTO transactions
  410. (price, ownable_id, amount)
  411. VALUES(?, ?, ?)
  412. ''', (price, ownable_id, amount,))
  413. # trigger stop loss orders
  414. cursor.execute('''
  415. UPDATE orders
  416. SET stop_loss = FALSE,
  417. "limit" = NULL
  418. WHERE stop_loss
  419. AND (buy AND "limit" > ?)
  420. OR (NOT buy AND "limit" < ?)
  421. (price, ownable_id, amount)
  422. VALUES(?, ?, ?)
  423. ''', (price, price,))
  424. def ownable_id_by_ownership_id(ownership_id):
  425. connect()
  426. cursor.execute('''
  427. SELECT ownable_id
  428. FROM ownership
  429. WHERE rowid = ?
  430. ''', (ownership_id,))
  431. return cursor.fetchone()[0]
  432. def place_order(buy, ownership_id, limit, stop_loss, amount):
  433. connect()
  434. cursor.execute('''
  435. INSERT INTO orders
  436. (buy, ownership_id, "limit", stop_loss, ordered_amount)
  437. VALUES (?, ?, ?, ?, ?)
  438. ''', (buy, ownership_id, limit, stop_loss, amount))
  439. execute_orders(ownable_id_by_ownership_id(ownership_id))
  440. return True