Commit 36b91198 authored by Alvin Natawiguna's avatar Alvin Natawiguna
Browse files

Bugfixes, new caching feature

parent c7d60101
This diff is collapsed.
import pymongo
import json
import bcrypt
import time
class Location(object):
def __init__(self, x, y):
......@@ -9,8 +10,19 @@ class Location(object):
self.x = int(x)
self.y = int(y)
def __eq__(self, other):
if isinstance(other, self.__class__):
return self.x == other.x and self.y == other.y
else:
raise TypeError("Unknown comparision between Item and {}".format(other.__class__))
def __ne__(self, other):
return self.__eq__(other)
class GameMap(object):
def __init__(self, name, width, height, locations = None):
from .item import ItemId, Item
assert width > 0 and height > 0
super().__init__()
......@@ -25,8 +37,13 @@ class GameMap(object):
assert len(locations) == width and len(locations[0]) == height
self.locations = []
for elem in locations:
self.locations.append(elem)
for row in locations:
mapRow = []
for elem in row:
itemId = ItemId.getIndex(elem)
mapRow.append(Item(id=itemId, count=1))
self.locations.append(mapRow)
class Game(object):
FILE_MAP = 'map.json'
......@@ -78,9 +95,8 @@ class Game(object):
cursor = db.get_collection(Game.DB_COLLECTION_USERS).find()
for user in cursor:
print(user)
location = Location(user[Player.KEY_CURRENTLOCATION]['x'], user[Player.KEY_CURRENTLOCATION]['y'])
self.players.append(Player(self, user['username'], user['password'].encode(), location))
self.players.append(Player(self, user[Player.KEY_USERNAME], user[Player.KEY_PASSWORD].encode(), location))
def __loadOffers(self):
pass
......@@ -104,15 +120,18 @@ class Game(object):
else:
raise LookupError('user not found: {}'.format(username))
if isinstnace(password, str):
if isinstance(password, str):
password = password.encode()
assert isinstance(password, bytes)
if bcrypt.hashpw(password, user.password.encode()) != user.password.encode():
hashed = bcrypt.hashpw(password, user.password)
if hashed != user.password:
raise ValueError('invalid password')
token = Player.__generateToken(username, password)
token = self.generateToken(username, password.decode('utf-8'))
token = token.decode('utf-8')
user.token = token
return token
......@@ -149,52 +168,61 @@ class Game(object):
username: the Player's username
"""
def getPlayer(self, token = None, username = None):
if token or username:
if token:
assert isinstance(token, str)
for idx, player in enumerate(self.players):
assert isinstance(player.username, str)
if player.username == username or (token and player.token == token):
if player.token == token:
return player
else:
return None
elif username:
assert isinstance(username, str)
for idx, player in enumerate(self.players):
if player.username == username:
return player
else:
return None
else:
raise TypeError('No search argument (token or username) defined')
def makeOffer(self, username, demandedItem, offeredItem):
from .item import ItemOffer, Item
with pymongo.MongoClient() as client:
db = client.get_database(Game.DB_NAME)
assert db.authenticate(Game.DB_USERNAME, Game.DB_PASSWORD)
# deduct the item from the db
result = db.users.update_one({
requestDict = [{
'username': username,
'items.id': offeredItem.id
'items.id': offeredItem.getId()
}, {
'$inc': {
'items.$.count': -offeredItem.count
'items.$.count': -offeredItem.count()
}
})
}]
result = db.users.update_one(requestDict)
assert result.modified_count == 1
newOffer = game.ItemOffer(username, demandedItem, offeredItem)
newOffer = ItemOffer(username, demandedItem, offeredItem)
self.offers.append(newOffer)
result = db.offers.insert_one({
'id': newOffer.__id,
requestDict = {
'id': newOffer.getId(),
'available': newOffer.available,
'username': newOffer.username,
'username': username,
'demand': {
'id': demandedItem.id,
'count': demandedItem.count
'id': demandedItem.getId(),
'count': demandedItem.count()
},
'offer': {
'id': offeredItem.id,
'count': offeredItem.count
'id': offeredItem.getId(),
'count': offeredItem.count()
}
})
}
result = db.offers.insert_one(requestDict)
assert result.inserted_count == 1
......@@ -214,12 +242,12 @@ class Game(object):
'available': False,
'username': 'DUPLICATE_EXTERNAL',
'demand': {
'id': demandedItem.id,
'count': demandedItem.count
'id': demandedItem.getId(),
'count': demandedItem.count()
},
'offer': {
'id': offeredItem.id,
'count': offeredItem.count
'id': offeredItem.getId(),
'count': offeredItem.count()
}
})
......@@ -237,4 +265,11 @@ class Game(object):
'id': offerId
})
assert result.deleted_count == 1
\ No newline at end of file
assert result.deleted_count == 1
def generateToken(self, username, password):
assert isinstance(password, str) and isinstance(username, str)
text = username + ':' + password + str(int(time.time()))
return bcrypt.hashpw(text.encode(), bcrypt.gensalt())
\ No newline at end of file
......@@ -33,5 +33,8 @@ class GameObject(object):
def isValidId(self, id):
return isinstance(id, str) or isinstance(id, int)
def getId(self):
return self.__id
def __str__(self):
return "[GameObject {id}]".format(id = self.id)
\ No newline at end of file
return "[GameObject {}]".format(self.__id)
\ No newline at end of file
from enum import Enum
import time
import game.game_object
......@@ -57,27 +58,21 @@ class Item(game.game_object.GameObject):
id: int = None,
itemId: ItemId = None,
item = None,
count: int = None):
count: int = 1):
if isinstance(item, self.__class__):
super().__init__(item.id)
super().__init__(item.getId())
super().__copyInit(item)
self.__copyInit(item)
elif isinstance(itemId, ItemId):
super().__init__(itemId.id)
super().__init__(itemId.index)
if isinstance(count, int):
self.__selfInit(count)
else:
self.__selfInit(1)
self.__selfInit(count)
elif game.game_object.GameObject.isValidId(id):
super().__init__(id)
if isinstance(count, int):
self.__selfInit(count)
else:
self.__selfInit(1)
self.__selfInit(count)
else:
raise TypeError('Invalid constructor parameter(s) for Item')
......@@ -98,7 +93,7 @@ class Item(game.game_object.GameObject):
else:
raise ValueError('Item count cannot be negative')
elif not newVal: # getter
return __count
return self.__count
else: # some other param
raise TypeError('Invalid parameter for newVal')
......@@ -107,13 +102,7 @@ class Item(game.game_object.GameObject):
@classmethod
def mixItems(self, item1, item2):
if not isinstance(item1, self.__class__):
raise TypeError('item1 is not an Item')
if not isinstance(item2, self.__class__):
raise TypeError('item2 is not an Item')
if item1.__count >= Item.MIX_USAGE and item2.__count >= Item.MIX_USAGE:
if item1.count() >= Item.MIX_USAGE and item2.count() >= Item.MIX_USAGE:
# define the recipe here
result = None
......@@ -151,15 +140,15 @@ class Item(game.game_object.GameObject):
result = Item(itemId=ItemId.PHILOSOPHER_STONE)
else:
raise ValueError('Unknown recipe for item {} and {}'.format(item1.id, item2.id))
raise ValueError('Unknown recipe for item {} and {}'.format(item1.getId(), item2.getId()))
# reduce
item1.__count -= Item.MIX_USAGE
item2.__count -= Item.MIX_USAGE
item1.count(item1.count() - Item.MIX_USAGE)
item2.count(item2.count() - Item.MIX_USAGE)
return result
else:
if item1.__count < Item.MIX_USAGE:
if item1.count() < Item.MIX_USAGE:
raise ValueError('not enough items for item1')
else:
raise ValueError('not enough items for item2')
......@@ -168,7 +157,7 @@ class Item(game.game_object.GameObject):
if isinstance(other, self.__class__):
return self.__dict__ == other.__dict__
elif isinstance(other, ItemId):
return self.__id == other.index or self.__id == other.idStr
return self.getId() == other.index or self.getId() == other.id
else:
raise TypeError("No comparision defined between Item and {}".format(other.__class__))
......@@ -178,7 +167,7 @@ class Item(game.game_object.GameObject):
@classmethod
def isEqual(self, item, itemId: ItemId):
if isinstance(item, Item) and isinstance(itemId, ItemId):
return item.__id == itemId.index or item.__id == itemId.id
return item.getId() == itemId.index or item.getId() == itemId.id
else:
raise TypeError('Invalid parameter(s)')
......@@ -210,9 +199,9 @@ class ItemOffer(game.game_object.GameObject):
result += (''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(6)))
md5hash = hashlib.md5()
md5hash.update(result)
md5hash.update(result.encode())
return str(md5hash.hexdigest())
def asList(self):
return [self.offer.__id, self.offer.count, self.demand.__id, self.demand.__count, self.avaliable, self.__id]
return [self.offer.getId(), self.offer.count(), self.demand.getId(), self.demand.count(), self.available, self.getId()]
......@@ -37,13 +37,15 @@ class Player(GameObject):
# some protos
self.token = None
self.items = []
self.lastMoveTime = 0
self.alreadyFielded = False
if not location:
# new user implied
self.game.Location = Location(0, 0)
self.location = Location(0, 0)
else:
# user already exists in db
self.game.Location = location
self.location = location
self.__loadProperties()
def __loadProperties(self):
......@@ -57,6 +59,7 @@ class Player(GameObject):
userDict = cursor.next()
self.__loadItems(db, userDict)
self.__loadLastMoveTime(db, userDict)
else:
if cursor.count() == 0:
raise LookupError('player not found: {}'.format(self.username))
......@@ -81,13 +84,31 @@ class Player(GameObject):
}
}
)
def __loadLastMoveTime(self, db, userDict):
from .item import Item
if 'lastMoveTime' in userDict:
self.lastMoveTime = userDict['lastMoveTime']
else:
# initialize empty inventory
db.get_collection(Game.DB_COLLECTION_USERS).update_one(
{
'_id': userDict['_id']
},
{
'$set': {
'lastMoveTime': 0
}
}
)
def getInventory(self):
inventory = []
from .item import Item
inventory = [0] * 10
for idx, item in enumerate(self.items):
for count in range(item.count):
inventory.append(item.id)
inventory[item.getId()] = item.count()
return inventory
......@@ -102,10 +123,10 @@ class Player(GameObject):
for offer in db.offers.find({'username': self.username}):
myOffers.append(ItemOffer(
offer['username'],
Item(offer['demand']['id'], offer['demand']['count']),
Item(offer['offer']['id'], offer['offer']['count']),
offer['id']
username = offer['username'],
demand = Item(id=offer['demand']['id'], count=offer['demand']['count']),
offer = Item(id=offer['offer']['id'], count=offer['offer']['count']),
id = offer['id']
))
return myOffers
......@@ -114,20 +135,40 @@ class Player(GameObject):
x : int = None,
y : int = None,
location : Location = None):
moveTime = int(time.time())
if isinstance(x, int) and isinstance(y, int):
if x in range(0, self.game.map.width) and y in range(0, self.game.map.height):
self.currentLocation.x = x
self.currentLocation.y = y
else: raise ValueError('Position out of bounds: ({},{})'.format(x, y))
if self.lastMoveTime > moveTime:
raise ValueError("Player still on the move")
if x == self.location.x and y == self.location.y:
raise ValueError("Position not changed")
else:
self.location.x = x
self.location.y = y
self.lastMoveTime = moveTime + 10
moveTime += 10
self.alreadyFielded = False
else:
raise ValueError('Position out of bounds: ({},{})'.format(x, y))
elif isinstance(location, Location):
if location.x in range(0, self.game.map.width) and y in range(0, self.game.map.height):
self.currentLocation = location
if self.lastMoveTime > moveTime:
raise ValueError("Player still on the move")
elif self.location == location:
raise ValueError("Position not changed")
else:
self.location = location
self.lastMoveTime = moveTime + 10
moveTime += 10
else:
raise ValueError('Position out of bounds: ({},{})'.format(location.x, location.y))
else:
raise ValueError('Invalid parameter')
return moveTime
def takeItem(self,
x : int = None,
y : int = None,
......@@ -139,27 +180,32 @@ class Player(GameObject):
x = location.x
y = location.y
item = self.game.map.locations[x][y]
self.addItem(item)
if self.lastMoveTime > int(time.time()):
raise ValueError("Player still on the move")
elif self.alreadyFielded:
raise ValueError("Already taken item on ({},{})".format(x, y))
else:
item = self.game.map.locations[x][y]
self.alreadyFielded = True
return item.id
return item.getId(), self.addItem(item)
elif offerId is str:
pass
else:
raise TypeError('Invalid parameters')
def getItem(self, itemId):
from .item import ItemId
from .item import ItemId, Item
retval = None
if isinstance(itemId, ItemId):
for idx, item in enumerate(self.items):
if itemId.index == item.id:
if itemId.index == item.getId():
retval = item
elif isinstance(itemId, int):
for idx, item in enumerate(self.items):
if itemId == item.id:
if itemId == item.getId():
retval = item
return retval
......@@ -168,29 +214,31 @@ class Player(GameObject):
item = None,
itemId: str = None,
count: int = 1):
from .item import Item
from .item import Item, ItemId
newItemId, newItemCount = None, None
if isinstance(item, Item):
newItemId, newItemCount = item.id, item.count
elif isinstance(id, str) and isinstance(count, int):
newItemId, newItemCount = id, count
newItemId, newItemCount = item.getId(), item.count()
elif isinstance(itemId, str) and isinstance(count, int):
newItemId, newItemCount = ItemId.getIndex(itemId), count
elif isinstance(itemId, int):
newItemId, newItemCount = itemId, 1
else:
raise TypeError('Invalid parameter')
assert isinstance(newItemId, int) and isinstance(newItemCount, int)
retval = False
new = False
for idx, item in enumerate(self.items):
if newItemId == item.id:
item += newItemCount
for idx, myItem in enumerate(self.items):
if newItemId == myItem.getId():
myItem.count(myItem.count() + newItemCount)
break
else:
self.items.append(Item(newItemId, newItemCount))
retval = True
new = True
return retval
return new
def mixItems(self, item1, item2):
from .item import Item
......@@ -198,39 +246,42 @@ class Player(GameObject):
# check if the player has the certain item
inventoryItem1, inventoryItem2 = None, None
assert item1.id != item2.id
assert item1 in self.items and item2 in self.items
for idx, item in enumerate(self.items):
if item1.id == item.id:
inventoryItem1 = item
elif item2.id == item.id:
inventoryItem2 = item
newItem = Item.mixItems(item1, item2)
return newItem, self.addItem(item = newItem)
if inventoryItem1 and inventoryItem2:
newItem = Item.mixItems(inventoryItem1, inventoryItem2)
def checkMakeItemOffer(self, offer):
retval = None
return newItem, self.addItem(itemId = newItem)
else:
if not inventoryItem1 and not inventoryItem2:
raise LookupError('Player does not have items {} and {}'.format(item1.id, item2.id))
else:
if not inventoryItem1:
raise LookupError('Player does not have item with id {}'.format(item1.id))
for idx, item in enumerate(self.items):
if item.getId() == offer.getId():
if item.count() >= offer.count():
retval = True
else:
raise LookupError('Player does not have item with id {}'.format(item2.id))
retval = False
break
else:
retval = False
def checkMakeItemOffer(self, offer):
return checkAcceptOfferedItem(offer)
print("[DEBUG]", retval)
return retval
def makeItemOffer(self, offeredItem):
acceptOffer(offeredItem)
assert self.checkMakeItemOffer(offeredItem)
for idx, item in enumerate(self.items):
if item.getId() == offeredItem.getId():
item.count(item.count() - offeredItem.count())
break
def checkAcceptOfferedItem(self, demandedItem):
retval = None
for idx, item in enumerate(self.items):
if item.id == demandedItem.id:
if item.count < demandedItem.count:
if item.getId() == demandedItem.getId():
if item.count() < demandedItem.count():
retval = False
else:
retval = True
......@@ -242,20 +293,19 @@ class Player(GameObject):
return retval
def acceptOffer(self, demandedItem):
assert checkAcceptOfferedItem(demandedItem)
assert self.checkAcceptOfferedItem(demandedItem)
for idx, item in enumerate(self.items):
if item.id == demandedItem.id:
item.count -= demandedItem.count
if item.getId() == demandedItem.getId():
item.count(item.count() - demandedItem.count())
break
def fetchOffer(self, offeredItem):
new = False
for idx, item in enumerate(self.items):
if item.id == offeredItem.id:
item.count += offeredItem.count
if item.getId() == offeredItem.getId():
item.count(item.count() + offeredItem.count())
break
else:
......@@ -264,18 +314,10 @@ class Player(GameObject):
return new
def cancelOffer(self, offeredItem):
return self.fetchOffer(offeredItem)
@classmethod
def generateId(self):
Player.COUNT += 1
return "P-{:03d}".format(Player.COUNT)