Finished implementing & testing QTree. Implemented pickable marker class.
This commit is contained in:
parent
6a35b11ec2
commit
1f6f51877a
36
src/Main.cpp
36
src/Main.cpp
@ -1,10 +1,42 @@
|
||||
#include <controller/FarmlandsGame.h>
|
||||
#include <utils/Exceptions.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
using namespace farmlands;
|
||||
|
||||
#include <utils/QTree.h>
|
||||
|
||||
void qtreeTest()
|
||||
{
|
||||
utils::RectF bounds(0, 0, 100, 100);
|
||||
utils::QTree<std::string, 10> tree(bounds);
|
||||
|
||||
Assert(tree.empty(), "Tree should be empty.");
|
||||
Assert(tree.size() == 0, "Tree should be empty.");
|
||||
srand(10);
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
tree.insert("Random", rand() % 100, rand() % 100);
|
||||
}
|
||||
|
||||
Assert(!tree.empty(), "Tree should be empty.");
|
||||
Assert(tree.size() == 100, "Tree should be empty.");
|
||||
|
||||
while (tree.size() > 2)
|
||||
tree.erase(tree.begin());
|
||||
|
||||
utils::RectF search(-10, -10, 120, 120);
|
||||
for (auto it = tree.lower_bound(search); it != tree.upper_bound(search); it++)
|
||||
std::cout << it->data << ": " << it->x << ", " << it->y << "\n";
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
qtreeTest();
|
||||
}
|
||||
|
||||
#if 0
|
||||
|
||||
int main()
|
||||
{
|
||||
int ret = 0;
|
||||
@ -32,3 +64,5 @@ int main()
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
49
src/components/items/Pickable.h
Normal file
49
src/components/items/Pickable.h
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Pickable.h
|
||||
*
|
||||
* Created on: Dec 13, 2016
|
||||
* Author: tibi
|
||||
*/
|
||||
|
||||
#ifndef COMPONENTS_ITEMS_PICKABLE_H_
|
||||
#define COMPONENTS_ITEMS_PICKABLE_H_
|
||||
|
||||
#include <model/Component.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace farmlands {
|
||||
namespace components {
|
||||
namespace items {
|
||||
|
||||
/**
|
||||
* Marks object as pickable.
|
||||
*
|
||||
* The class is simply a marker class and doesn't do anything.
|
||||
*/
|
||||
class Pickable: public model::Component
|
||||
{
|
||||
public:
|
||||
virtual ~Pickable() { }
|
||||
virtual model::Component* clone() override;
|
||||
virtual void dump(unsigned level) override;
|
||||
};
|
||||
|
||||
inline model::Component* Pickable::clone()
|
||||
{
|
||||
return new Pickable();
|
||||
}
|
||||
|
||||
inline void Pickable::dump(unsigned level)
|
||||
{
|
||||
for (unsigned i = 0; i < level; i++)
|
||||
std::cout<<" ";
|
||||
|
||||
std::cout << " .Component: Pickable\n";
|
||||
}
|
||||
|
||||
} /* namespace items */
|
||||
} /* namespace components */
|
||||
} /* namespace farmlands */
|
||||
|
||||
#endif /* COMPONENTS_ITEMS_PICKABLE_H_ */
|
@ -359,6 +359,17 @@ components::items::Item* parse<components::items::Item> (boost::property_tree::p
|
||||
}
|
||||
|
||||
|
||||
template <>
|
||||
components::items::Pickable* parse<components::items::Pickable> (boost::property_tree::ptree& root)
|
||||
{
|
||||
// Ensure we are on the correct node (property tree seems to add root of its own)
|
||||
if (root.size() > 0 && root.front().first == "Pickaxe")
|
||||
root = root.front().second;
|
||||
|
||||
// Parse
|
||||
return new components::items::Pickable();
|
||||
}
|
||||
|
||||
template <>
|
||||
components::items::Pickaxe* parse<components::items::Pickaxe> (boost::property_tree::ptree& root)
|
||||
{
|
||||
@ -580,6 +591,9 @@ model::GameObject* parse<model::GameObject> (boost::property_tree::ptree& root)
|
||||
else if (child.first == "Item")
|
||||
gameObj->addComponent(parse<components::items::Item>(child.second));
|
||||
|
||||
else if (child.first == "Pickable")
|
||||
gameObj->addComponent(parse<components::items::Pickable>(child.second));
|
||||
|
||||
else if (child.first == "Pickaxe")
|
||||
gameObj->addComponent(parse<components::items::Pickaxe>(child.second));
|
||||
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include <components/items/Giftable.h>
|
||||
#include <components/items/Hoe.h>
|
||||
#include <components/items/Item.h>
|
||||
#include <components/items/Pickable.h>
|
||||
#include <components/items/Pickaxe.h>
|
||||
#include <components/items/Scythe.h>
|
||||
#include <components/items/WateringCan.h>
|
||||
@ -91,6 +92,9 @@ namespace storage {
|
||||
template <>
|
||||
components::items::Item* parse<components::items::Item> (boost::property_tree::ptree& root);
|
||||
|
||||
template <>
|
||||
components::items::Pickable* parse<components::items::Pickable> (boost::property_tree::ptree& root);
|
||||
|
||||
template <>
|
||||
components::items::Pickaxe* parse<components::items::Pickaxe> (boost::property_tree::ptree& root);
|
||||
|
||||
|
@ -15,9 +15,6 @@
|
||||
namespace farmlands {
|
||||
namespace utils {
|
||||
|
||||
#if 0
|
||||
// Commented because no longer needed.
|
||||
|
||||
template <typename T>
|
||||
struct QTreeItem
|
||||
{
|
||||
@ -25,6 +22,39 @@ namespace utils {
|
||||
float x, y;
|
||||
};
|
||||
|
||||
template <typename T, size_t Capacity>
|
||||
class QTree;
|
||||
|
||||
template <typename T, size_t Capacity>
|
||||
class QTreeIterator
|
||||
{
|
||||
public:
|
||||
QTreeIterator();
|
||||
QTreeIterator(const QTreeIterator& other);
|
||||
QTreeIterator(QTree<T, Capacity>* tree, const RectF& rect, size_t item = 0);
|
||||
QTreeIterator& operator= (const QTreeIterator& other);
|
||||
|
||||
bool operator== (const QTreeIterator& other);
|
||||
bool operator!= (const QTreeIterator& other);
|
||||
|
||||
QTreeItem<T>& operator*();
|
||||
QTreeItem<T>* operator->();
|
||||
|
||||
QTreeIterator& operator++();
|
||||
QTreeIterator operator++(int);
|
||||
|
||||
private:
|
||||
void moveFirst();
|
||||
void moveNext();
|
||||
ssize_t moveUp(); // Returns index of child
|
||||
|
||||
RectF m_rect;
|
||||
QTree<T, Capacity>* m_tree;
|
||||
ssize_t m_item;
|
||||
|
||||
friend class QTree<T, Capacity>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Quad tree
|
||||
*/
|
||||
@ -32,7 +62,7 @@ namespace utils {
|
||||
class QTree : public INonAssignable
|
||||
{
|
||||
public:
|
||||
typedef int iterator;
|
||||
typedef QTreeIterator<T, Capacity> iterator;
|
||||
|
||||
QTree(const RectF& bounds);
|
||||
virtual ~QTree();
|
||||
@ -49,32 +79,190 @@ namespace utils {
|
||||
void clear();
|
||||
|
||||
iterator find(T element);
|
||||
iterator find(float x, float y);
|
||||
|
||||
iterator lower_bound(float x, float y);
|
||||
iterator lower_bound(float x, float y, float distance);
|
||||
iterator lower_bound(const RectF& area);
|
||||
|
||||
iterator upper_bound(float x, float y);
|
||||
iterator upper_bound(float x, float y, float distance);
|
||||
iterator upper_bound(const RectF& area);
|
||||
|
||||
private:
|
||||
void subdivide();
|
||||
void checkMerge();
|
||||
void merge();
|
||||
|
||||
RectF m_bounds;
|
||||
bool m_isSplit;
|
||||
QTree* m_children[4];
|
||||
QTreeItem m_items[Capacity];
|
||||
QTree* m_parent;
|
||||
QTreeItem<T> m_items[Capacity];
|
||||
size_t m_itemsCount;
|
||||
|
||||
friend class QTreeIterator<T, Capacity>;
|
||||
};
|
||||
|
||||
|
||||
/****** Iterator implementation ******/
|
||||
|
||||
template<typename T, size_t Capacity>
|
||||
QTreeIterator<T, Capacity>::QTreeIterator()
|
||||
: m_rect(),
|
||||
m_tree(nullptr),
|
||||
m_item(0)
|
||||
{
|
||||
}
|
||||
|
||||
template<typename T, size_t Capacity>
|
||||
QTreeIterator<T, Capacity>::QTreeIterator(const QTreeIterator<T, Capacity>& other)
|
||||
: m_rect(other.m_rect),
|
||||
m_tree(other.m_tree),
|
||||
m_item(other.m_item)
|
||||
{
|
||||
}
|
||||
|
||||
template<typename T, size_t Capacity>
|
||||
QTreeIterator<T, Capacity>::QTreeIterator(QTree<T, Capacity>* tree, const RectF& rect, size_t item)
|
||||
: m_rect(rect),
|
||||
m_tree(tree),
|
||||
m_item(item)
|
||||
{
|
||||
moveFirst();
|
||||
}
|
||||
|
||||
template<typename T, size_t Capacity>
|
||||
QTreeIterator<T, Capacity>& QTreeIterator<T, Capacity>::operator=(const QTreeIterator<T, Capacity>& other)
|
||||
{
|
||||
if (&other != this)
|
||||
{
|
||||
m_rect = other.m_rect;
|
||||
m_tree = other.m_tree;
|
||||
m_item = other.m_item;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T, size_t Capacity>
|
||||
bool QTreeIterator<T, Capacity>::operator==(const QTreeIterator<T, Capacity>& other)
|
||||
{
|
||||
bool equal = (m_rect == other.m_rect && m_tree == other.m_tree);
|
||||
if (equal && m_tree != nullptr)
|
||||
equal &= (m_item == other.m_item);
|
||||
|
||||
return equal;
|
||||
}
|
||||
|
||||
template<typename T, size_t Capacity>
|
||||
bool QTreeIterator<T, Capacity>::operator!=(const QTreeIterator<T, Capacity>& other)
|
||||
{
|
||||
return !operator==(other);
|
||||
}
|
||||
|
||||
template<typename T, size_t Capacity>
|
||||
QTreeItem<T>& QTreeIterator<T, Capacity>::operator*()
|
||||
{
|
||||
Assert(m_tree != nullptr, "Bad iterator.");
|
||||
return m_tree->m_items[m_item];
|
||||
}
|
||||
|
||||
template<typename T, size_t Capacity>
|
||||
QTreeItem<T>* QTreeIterator<T, Capacity>::operator->()
|
||||
{
|
||||
Assert(m_tree != nullptr, "Bad iterator.");
|
||||
return &m_tree->m_items[m_item];
|
||||
}
|
||||
|
||||
template<typename T, size_t Capacity>
|
||||
QTreeIterator<T, Capacity>& QTreeIterator<T, Capacity>::operator++()
|
||||
{
|
||||
moveNext();
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T, size_t Capacity>
|
||||
QTreeIterator<T, Capacity> QTreeIterator<T, Capacity>::operator++(int)
|
||||
{
|
||||
auto old = *this;
|
||||
moveNext();
|
||||
return old;
|
||||
}
|
||||
|
||||
template<typename T, size_t Capacity>
|
||||
void QTreeIterator<T, Capacity>::moveFirst()
|
||||
{
|
||||
if (m_tree && m_tree->m_isSplit)
|
||||
moveNext();
|
||||
}
|
||||
|
||||
template<typename T, size_t Capacity>
|
||||
void QTreeIterator<T, Capacity>::moveNext()
|
||||
{
|
||||
Assert(m_tree != nullptr, "Bad iterator");
|
||||
|
||||
bool done = false;
|
||||
ssize_t childIndex = -1;
|
||||
|
||||
do
|
||||
{
|
||||
if (m_tree->m_isSplit)
|
||||
{
|
||||
// Move to next child
|
||||
++childIndex;
|
||||
|
||||
// Reached end of current node?
|
||||
if (childIndex >= 4)
|
||||
{
|
||||
childIndex = moveUp();
|
||||
}
|
||||
else if (m_rect.intersects(m_tree->m_children[childIndex]->m_bounds))
|
||||
{
|
||||
// Move down
|
||||
m_tree = m_tree->m_children[childIndex];
|
||||
m_item = -1;
|
||||
childIndex = -1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Move on to next element
|
||||
++m_item;
|
||||
|
||||
// Reached end of current node?
|
||||
if (m_item >= m_tree->m_itemsCount)
|
||||
{
|
||||
childIndex = moveUp();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Reached the end when there are no more nodes, or when we found a good item
|
||||
done = m_rect.contains(m_tree->m_items[m_item].x, m_tree->m_items[m_item].y);
|
||||
}
|
||||
|
||||
} while (!done && m_tree);
|
||||
}
|
||||
|
||||
template<typename T, size_t Capacity>
|
||||
ssize_t QTreeIterator<T, Capacity>::moveUp()
|
||||
{
|
||||
auto child = m_tree;
|
||||
m_tree = m_tree->m_parent;
|
||||
m_item = -1;
|
||||
|
||||
if (m_tree == nullptr)
|
||||
return -1;
|
||||
|
||||
// Figure out which node was the child
|
||||
for (ssize_t i = 0; i < 4; i++)
|
||||
if (m_tree->m_children[i] == child)
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/****** Container implementation ******/
|
||||
|
||||
template<typename T, size_t Capacity>
|
||||
QTree<T, Capacity>::QTree(const RectF& bounds)
|
||||
: m_bounds(bounds),
|
||||
m_isSplit(false),
|
||||
m_children(),
|
||||
m_parent(nullptr),
|
||||
m_items(),
|
||||
m_itemsCount(0)
|
||||
{
|
||||
@ -91,29 +279,40 @@ namespace utils {
|
||||
}
|
||||
|
||||
template<typename T, size_t Capacity>
|
||||
inline QTree<T, Capacity>::iterator QTree<T, Capacity>::begin()
|
||||
typename QTree<T, Capacity>::iterator QTree<T, Capacity>::begin()
|
||||
{
|
||||
return iterator(this, m_bounds);
|
||||
}
|
||||
|
||||
template<typename T, size_t Capacity>
|
||||
inline QTree<T, Capacity>::iterator QTree<T, Capacity>::end()
|
||||
typename QTree<T, Capacity>::iterator QTree<T, Capacity>::end()
|
||||
{
|
||||
return iterator(nullptr, m_bounds);
|
||||
}
|
||||
|
||||
template<typename T, size_t Capacity>
|
||||
inline bool QTree<T, Capacity>::empty() const
|
||||
bool QTree<T, Capacity>::empty() const
|
||||
{
|
||||
return (m_itemsCount == 0);
|
||||
return !m_isSplit && (m_itemsCount == 0);
|
||||
}
|
||||
|
||||
template<typename T, size_t Capacity>
|
||||
inline size_t QTree<T, Capacity>::size() const
|
||||
size_t QTree<T, Capacity>::size() const
|
||||
{
|
||||
if (m_isSplit)
|
||||
{
|
||||
size_t size = 0;
|
||||
for (size_t i = 0; i < 4; i++)
|
||||
size += m_children[i]->size();
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
return m_itemsCount;
|
||||
}
|
||||
|
||||
template<typename T, size_t Capacity>
|
||||
inline void QTree<T, Capacity>::insert(T element, float x, float y)
|
||||
void QTree<T, Capacity>::insert(T element, float x, float y)
|
||||
{
|
||||
Assert(m_bounds.contains(x, y), "Can't add element outside bounds.");
|
||||
|
||||
@ -141,18 +340,39 @@ namespace utils {
|
||||
};
|
||||
|
||||
m_items[m_itemsCount] = item;
|
||||
}
|
||||
|
||||
m_itemsCount++;
|
||||
}
|
||||
|
||||
template<typename T, size_t Capacity>
|
||||
inline void QTree<T, Capacity>::erase(iterator it)
|
||||
{
|
||||
}
|
||||
|
||||
template<typename T, size_t Capacity>
|
||||
inline void QTree<T, Capacity>::clear()
|
||||
void QTree<T, Capacity>::erase(iterator it)
|
||||
{
|
||||
// Tell correct container to erase item
|
||||
if (it.m_tree == this)
|
||||
{
|
||||
Assert(!m_isSplit, "Container modified. Cannot erase element.");
|
||||
Assert(it.m_item < m_itemsCount, "Container modified. Cannot erase element.");
|
||||
|
||||
// Move another element in the erased position
|
||||
if (it.m_item < m_itemsCount - 1)
|
||||
{
|
||||
m_items[it.m_item] = m_items[m_itemsCount - 1];
|
||||
|
||||
}
|
||||
--m_itemsCount;
|
||||
|
||||
if (m_parent)
|
||||
m_parent->checkMerge();
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert(m_isSplit, "Container modified or bad iterator. Cannot erase element.");
|
||||
it.m_tree->erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, size_t Capacity>
|
||||
void QTree<T, Capacity>::clear()
|
||||
{
|
||||
if (m_isSplit)
|
||||
{
|
||||
@ -165,43 +385,25 @@ namespace utils {
|
||||
}
|
||||
|
||||
template<typename T, size_t Capacity>
|
||||
inline QTree<T, Capacity>::iterator QTree<T, Capacity>::find(T element)
|
||||
typename QTree<T, Capacity>::iterator QTree<T, Capacity>::find(T element)
|
||||
{
|
||||
for (auto it = begin(); it != end(); it++)
|
||||
if (it->data == element)
|
||||
return it;
|
||||
|
||||
return end();
|
||||
}
|
||||
|
||||
template<typename T, size_t Capacity>
|
||||
inline QTree<T, Capacity>::iterator QTree<T, Capacity>::find(float x, float y)
|
||||
typename QTree<T, Capacity>::iterator QTree<T, Capacity>::lower_bound(const RectF& area)
|
||||
{
|
||||
return iterator(this, area);
|
||||
}
|
||||
|
||||
template<typename T, size_t Capacity>
|
||||
inline QTree<T, Capacity>::iterator QTree<T, Capacity>::lower_bound(float x, float y)
|
||||
{
|
||||
}
|
||||
|
||||
template<typename T, size_t Capacity>
|
||||
inline QTree<T, Capacity>::iterator QTree<T, Capacity>::lower_bound(float x, float y, float distance)
|
||||
{
|
||||
}
|
||||
|
||||
template<typename T, size_t Capacity>
|
||||
inline QTree<T, Capacity>::iterator QTree<T, Capacity>::lower_bound(const RectF& area)
|
||||
{
|
||||
}
|
||||
|
||||
template<typename T, size_t Capacity>
|
||||
inline QTree<T, Capacity>::iterator QTree<T, Capacity>::upper_bound(float x, float y)
|
||||
{
|
||||
}
|
||||
|
||||
template<typename T, size_t Capacity>
|
||||
inline QTree<T, Capacity>::iterator QTree<T, Capacity>::upper_bound(float x, float y, float distance)
|
||||
{
|
||||
}
|
||||
|
||||
template<typename T, size_t Capacity>
|
||||
inline QTree<T, Capacity>::iterator QTree<T, Capacity>::upper_bound(const RectF& area)
|
||||
typename QTree<T, Capacity>::iterator QTree<T, Capacity>::upper_bound(const RectF& area)
|
||||
{
|
||||
return iterator(nullptr, area);
|
||||
}
|
||||
|
||||
template<typename T, size_t Capacity>
|
||||
@ -222,7 +424,10 @@ namespace utils {
|
||||
|
||||
// Allocate subtrees
|
||||
for (size_t i = 0; i < 4; i++)
|
||||
{
|
||||
m_children[i] = new QTree<T, Capacity>(rects[i]);
|
||||
m_children[i]->m_parent = this;
|
||||
}
|
||||
|
||||
// Set split flag
|
||||
m_isSplit = true;
|
||||
@ -235,6 +440,25 @@ namespace utils {
|
||||
insert(m_items[i].data, m_items[i].x, m_items[i].y);
|
||||
}
|
||||
|
||||
template<typename T, size_t Capacity>
|
||||
void QTree<T, Capacity>::checkMerge()
|
||||
{
|
||||
// The rules for merging are:
|
||||
// - none of the children is split
|
||||
// - the total number of elements is less than half the capacity
|
||||
bool canMerge = true;
|
||||
size_t accumSize = 0;
|
||||
|
||||
for (size_t i = 0; i < 4; i++)
|
||||
{
|
||||
canMerge = canMerge && (!m_children[i]->m_isSplit);
|
||||
accumSize += m_children[i]->m_itemsCount;
|
||||
}
|
||||
|
||||
if (canMerge && accumSize < Capacity / 2)
|
||||
merge();
|
||||
}
|
||||
|
||||
template<typename T, size_t Capacity>
|
||||
void QTree<T, Capacity>::merge()
|
||||
{
|
||||
@ -248,18 +472,20 @@ namespace utils {
|
||||
|
||||
for (size_t i = 0; i < 4; i++)
|
||||
{
|
||||
Assert(!m_children[i].m_isSplit, "Cannot merge if children are split!!!");
|
||||
Assert(!m_children[i]->m_isSplit, "Cannot merge if children are split!!!");
|
||||
|
||||
for (size_t j = 0; j < m_children[i].m_itemsCount; j++)
|
||||
insert(m_children[i].m_items[j].data, m_children[i].m_items[j].x, m_children[i].m_items[j].y);
|
||||
for (size_t j = 0; j < m_children[i]->m_itemsCount; j++)
|
||||
insert(m_children[i]->m_items[j].data, m_children[i]->m_items[j].x, m_children[i]->m_items[j].y);
|
||||
|
||||
delete m_children[i];
|
||||
m_children[i] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} /* namespace utils */
|
||||
} /* namespace farmlands */
|
||||
|
||||
|
||||
|
||||
#endif /* UTILS_QTREE_H_ */
|
||||
|
@ -22,15 +22,15 @@ namespace utils {
|
||||
Rect(T x, T y, T w, T h)
|
||||
: x(x), y(y), w(w), h(h) { }
|
||||
|
||||
bool contains(T px, T py)
|
||||
bool contains(T px, T py) const
|
||||
{
|
||||
bool containsX = (x <= px && px <= x + w);
|
||||
bool containsY = (y <= px && px <= x + w);
|
||||
bool containsY = (y <= py && py <= y + h);
|
||||
|
||||
return containsX && containsY;
|
||||
}
|
||||
|
||||
bool intersects(const Rect<T> other)
|
||||
bool intersects(const Rect<T>& other) const
|
||||
{
|
||||
bool intersectsX = (x <= other.x && other.x <= x + w) || (other.x <= x && other.x + other.w >= x);
|
||||
bool intersectsY = (y <= other.y && other.y <= y + h) || (other.y <= y && other.y + other.h >= y);
|
||||
@ -38,6 +38,16 @@ namespace utils {
|
||||
return intersectsX && intersectsY;
|
||||
}
|
||||
|
||||
bool operator==(const Rect<T>& other) const
|
||||
{
|
||||
return (x == other.x) && (y == other.y) && (w == other.w) && (h == other.h);
|
||||
}
|
||||
|
||||
bool operator!=(const Rect<T>& other) const
|
||||
{
|
||||
return !operator==(other);
|
||||
}
|
||||
|
||||
// Values
|
||||
T x, y, w, h;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user