model.py 15 KB

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