1173 lines
40 KiB
C++
1173 lines
40 KiB
C++
|
|
#include "snake.h"
|
|
|
|
#include <QGraphicsPixmapItem>
|
|
#include <QGraphicsScene>
|
|
|
|
BodyPart::BodyPart( const unsigned x, const unsigned y, const Direction d, const Direction pd, QGraphicsPixmapItem*const img ) noexcept
|
|
: x{ x }
|
|
, y{ y }
|
|
, direction{ d }
|
|
, prev_direction{ pd }
|
|
, image{ img }
|
|
{
|
|
|
|
}
|
|
BodyPart::BodyPart( BodyPart&& other ) noexcept
|
|
: x{ other.x }
|
|
, y{ other.y }
|
|
, direction{ other.direction }
|
|
, prev_direction{ other.prev_direction }
|
|
, image{ other.image }
|
|
{
|
|
other.image = nullptr;
|
|
}
|
|
BodyPart& BodyPart::operator=( BodyPart&& other ) noexcept
|
|
{
|
|
if ( this == &other ) return *this;
|
|
this->x = other.x;
|
|
this->y = other.y;
|
|
this->direction = other.direction;
|
|
this->prev_direction = other.prev_direction;
|
|
this->image = other.image;
|
|
other.image = nullptr;
|
|
return *this;
|
|
}
|
|
void BodyPart::update( const unsigned new_x, const unsigned new_y, const Direction& new_direction ) noexcept {
|
|
this->x = new_x;
|
|
this->y = new_y;
|
|
this->image->setOffset( 16+(new_x*32), 16+(new_y*32) );
|
|
this->prev_direction = this->direction;
|
|
this->direction = new_direction;
|
|
}
|
|
|
|
|
|
Snake::Snake( const bool is_adversary ) noexcept
|
|
: adversary( is_adversary )
|
|
{
|
|
if ( is_adversary ) {
|
|
// only AI needs to keep track of the field state
|
|
this->field_map.fill( std::array<Tile, 16ul>{} );
|
|
}
|
|
}
|
|
|
|
|
|
const QPixmap& Snake::getHeadImage() const noexcept
|
|
{
|
|
if ( this->adversary ) {
|
|
return this->img_snakeHead_;
|
|
} else {
|
|
return this->img_snakeHead;
|
|
}
|
|
}
|
|
|
|
|
|
bool Snake::inTile( const unsigned x, const unsigned y, const bool avoid_tail ) const noexcept
|
|
{
|
|
if ( this->size() > 0 ) {
|
|
size_t i{ 0 };
|
|
size_t max{ this->size()-1ul };
|
|
if ( !avoid_tail ) {
|
|
++ max;
|
|
}
|
|
for ( auto bp{ this->cbegin() }; bp != this->cend(); ++bp ) {
|
|
if ( bp->x == x && bp->y == y ) {
|
|
return true;
|
|
}
|
|
++i;
|
|
if ( i >= max ) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
void Snake::setDirection( const Direction new_direction ) noexcept
|
|
{
|
|
this->head_direction = new_direction;
|
|
}
|
|
const Direction& Snake::direction() const noexcept
|
|
{
|
|
return this->head_direction;
|
|
}
|
|
|
|
void Snake::willGrow() noexcept
|
|
{
|
|
this->will_grow |= true;
|
|
}
|
|
void Snake::grow( const bool is_borning )
|
|
{
|
|
// build from the tail
|
|
const BodyPart& tail{ this->back() };
|
|
unsigned x{ tail.x };
|
|
unsigned y{ tail.y };
|
|
const Direction d{ tail.direction };
|
|
const Direction ld{ tail.prev_direction };
|
|
if ( is_borning ) {
|
|
// one tile behind
|
|
switch ( d ) {
|
|
case Direction::UP:
|
|
++ y;
|
|
break;
|
|
case Direction::DOWN:
|
|
-- y;
|
|
break;
|
|
case Direction::LEFT:
|
|
++ x;
|
|
break;
|
|
case Direction::RIGHT:
|
|
-- x;
|
|
break;
|
|
default:
|
|
// should be unreachable
|
|
throw("Unexpected direction: "+std::to_string(d));
|
|
}
|
|
}
|
|
this->emplace_back(
|
|
x, y,
|
|
d, ld,
|
|
new QGraphicsPixmapItem( (this->adversary) ? this->img_snakeTail_ : this->img_snakeTail )
|
|
);
|
|
this->update( nullptr, true );
|
|
this->back().update( x, y, d );
|
|
}
|
|
|
|
|
|
void Snake::update( QGraphicsScene* field_scene, const bool dry , const bool is_borning )
|
|
{
|
|
// grow if planned
|
|
if ( this->will_grow ) {
|
|
this->will_grow &= false;
|
|
this->grow( is_borning );
|
|
field_scene->addItem( this->back().image );
|
|
}
|
|
// anyway, update the whole body
|
|
size_t i{ 0 };
|
|
const size_t max_i{ this->size()-1ul };
|
|
unsigned new_x, prev_x, new_y, prev_y;
|
|
Direction new_direction, prev_direction, prev_body_d;
|
|
const QPixmap& head_img{ (this->adversary) ? this->img_snakeHead_ : this->img_snakeHead };
|
|
const QPixmap& body_img{ (this->adversary) ? this->img_snakeBody_ : this->img_snakeBody };
|
|
const QPixmap& curve_img{ (this->adversary) ? this->img_snakeCurve_ : this->img_snakeCurve };
|
|
const QPixmap& tail_img{ (this->adversary) ? this->img_snakeTail_ : this->img_snakeTail };
|
|
for ( auto bp{ this->begin() }; bp != this->end(); ++bp ) {
|
|
if ( ! dry ) {
|
|
// future position
|
|
if ( i == 0ul ) {
|
|
// head doesn't follow any other part of the body
|
|
switch ( this->head_direction ) {
|
|
case Direction::UP:
|
|
new_y = bp->y - 1u;
|
|
new_x = bp->x;
|
|
break;
|
|
case Direction::DOWN:
|
|
new_y = bp->y + 1u;
|
|
new_x = bp->x;
|
|
break;
|
|
case Direction::LEFT:
|
|
new_x = bp->x - 1u;
|
|
new_y = bp->y;
|
|
break;
|
|
case Direction::RIGHT:
|
|
new_x = bp->x + 1u;
|
|
new_y = bp->y;
|
|
break;
|
|
default:
|
|
// should be unreachable
|
|
throw("Unexpected direction: "+std::to_string(this->head_direction));
|
|
}
|
|
new_direction = this->head_direction;
|
|
} else {
|
|
// follow the previous part of the body
|
|
new_x = prev_x;
|
|
new_y = prev_y;
|
|
new_direction = prev_direction;
|
|
}
|
|
// store for the next part
|
|
prev_x = bp->x;
|
|
prev_y = bp->y;
|
|
prev_direction = bp->direction;
|
|
|
|
// update the body-part position
|
|
bp->update( new_x, new_y, new_direction );
|
|
}
|
|
|
|
// finally set the image to be shown
|
|
switch ( bp->direction ) {
|
|
|
|
case Direction::UP:
|
|
if ( i == 0ul ) {
|
|
bp->image->setPixmap(
|
|
head_img );
|
|
} else if ( i == max_i ) {
|
|
switch ( prev_body_d ) {
|
|
case Direction::UP:
|
|
bp->image->setPixmap(
|
|
tail_img );
|
|
break;
|
|
case Direction::LEFT:
|
|
bp->image->setPixmap(
|
|
tail_img.transformed(
|
|
QTransform().rotate( -90.0 ) ) );
|
|
break;
|
|
case Direction::RIGHT:
|
|
bp->image->setPixmap(
|
|
tail_img.transformed(
|
|
QTransform().rotate( 90.0 ) ) );
|
|
break;
|
|
default:
|
|
// should be unreachable
|
|
throw("Unexpected direction: "+std::to_string(prev_body_d));
|
|
}
|
|
} else {
|
|
switch ( prev_body_d ) {
|
|
case Direction::UP:
|
|
bp->image->setPixmap(
|
|
body_img );
|
|
break;
|
|
case Direction::LEFT:
|
|
bp->image->setPixmap(
|
|
curve_img.transformed(
|
|
QTransform().rotate( 90.0 ) ) );
|
|
break;
|
|
case Direction::RIGHT:
|
|
bp->image->setPixmap(
|
|
curve_img );
|
|
break;
|
|
default:
|
|
// should be unreachable
|
|
throw("Unexpected direction: "+std::to_string(prev_body_d));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Direction::DOWN:
|
|
if ( i == 0ul ) {
|
|
bp->image->setPixmap(
|
|
head_img.transformed(
|
|
QTransform().rotate( 180.0 ) ) );
|
|
} else if ( i == max_i ) {
|
|
switch ( prev_body_d ) {
|
|
case Direction::DOWN:
|
|
bp->image->setPixmap(
|
|
tail_img.transformed(
|
|
QTransform().rotate( 180.0 ) ) );
|
|
break;
|
|
case Direction::LEFT:
|
|
bp->image->setPixmap(
|
|
tail_img.transformed(
|
|
QTransform().rotate( -90.0 ) ) );
|
|
break;
|
|
case Direction::RIGHT:
|
|
bp->image->setPixmap(
|
|
tail_img.transformed(
|
|
QTransform().rotate( 90.0 ) ) );
|
|
break;
|
|
default:
|
|
// should be unreachable
|
|
throw("Unexpected direction: "+std::to_string(prev_body_d));
|
|
}
|
|
} else {
|
|
switch ( prev_body_d ) {
|
|
case Direction::DOWN:
|
|
bp->image->setPixmap(
|
|
body_img );
|
|
break;
|
|
case Direction::LEFT:
|
|
bp->image->setPixmap(
|
|
curve_img.transformed(
|
|
QTransform().rotate( 180.0 ) ) );
|
|
break;
|
|
case Direction::RIGHT:
|
|
bp->image->setPixmap(
|
|
curve_img.transformed(
|
|
QTransform().rotate( -90.0 ) ) );
|
|
break;
|
|
default:
|
|
// should be unreachable
|
|
throw("Unexpected direction: "+std::to_string(prev_body_d));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Direction::LEFT:
|
|
if ( i == 0ul ) {
|
|
bp->image->setPixmap(
|
|
head_img.transformed(
|
|
QTransform().rotate( -90.0 ) ) );
|
|
} else if ( i == max_i ) {
|
|
switch ( prev_body_d ) {
|
|
case Direction::LEFT:
|
|
bp->image->setPixmap(
|
|
tail_img.transformed(
|
|
QTransform().rotate( -90.0 ) ) );
|
|
break;
|
|
case Direction::UP:
|
|
bp->image->setPixmap(
|
|
tail_img );
|
|
break;
|
|
case Direction::DOWN:
|
|
bp->image->setPixmap(
|
|
tail_img.transformed(
|
|
QTransform().rotate( 180.0 ) ) );
|
|
break;
|
|
default:
|
|
// should be unreachable
|
|
throw("Unexpected direction: "+std::to_string(prev_body_d));
|
|
}
|
|
} else {
|
|
switch ( prev_body_d ) {
|
|
case Direction::LEFT:
|
|
bp->image->setPixmap(
|
|
body_img.transformed(
|
|
QTransform().rotate( -90.0 ) ) );
|
|
break;
|
|
case Direction::UP:
|
|
bp->image->setPixmap(
|
|
curve_img.transformed(
|
|
QTransform().rotate( -90.0 ) ) );
|
|
break;
|
|
case Direction::DOWN:
|
|
bp->image->setPixmap(
|
|
curve_img );
|
|
break;
|
|
default:
|
|
// should be unreachable
|
|
throw("Unexpected direction: "+std::to_string(prev_body_d));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Direction::RIGHT:
|
|
if ( i == 0ul ) {
|
|
bp->image->setPixmap(
|
|
head_img.transformed(
|
|
QTransform().rotate( 90.0 ) ) );
|
|
} else if ( i == max_i ) {
|
|
switch ( prev_body_d ) {
|
|
case Direction::RIGHT:
|
|
bp->image->setPixmap(
|
|
tail_img.transformed(
|
|
QTransform().rotate( 90.0 ) ) );
|
|
break;
|
|
case Direction::UP:
|
|
bp->image->setPixmap(
|
|
tail_img );
|
|
break;
|
|
case Direction::DOWN:
|
|
bp->image->setPixmap(
|
|
tail_img.transformed(
|
|
QTransform().rotate( 180.0 ) ) );
|
|
break;
|
|
default:
|
|
// should be unreachable
|
|
throw("Unexpected direction: "+std::to_string(prev_body_d));
|
|
}
|
|
} else {
|
|
switch ( prev_body_d ) {
|
|
case Direction::RIGHT:
|
|
bp->image->setPixmap(
|
|
body_img.transformed(
|
|
QTransform().rotate( 90.0 ) ) );
|
|
break;
|
|
case Direction::UP:
|
|
bp->image->setPixmap(
|
|
curve_img.transformed(
|
|
QTransform().rotate( 180.0 ) ) );
|
|
break;
|
|
case Direction::DOWN:
|
|
bp->image->setPixmap(
|
|
curve_img.transformed(
|
|
QTransform().rotate( 90.0 ) ) );
|
|
break;
|
|
default:
|
|
// should be unreachable
|
|
throw("Unexpected direction: "+std::to_string(prev_body_d));
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// should be unreachable
|
|
throw("Unexpected direction: "+std::to_string(bp->direction));
|
|
}
|
|
prev_body_d = bp->direction;
|
|
++i;
|
|
}
|
|
}
|
|
|
|
|
|
void Snake::move( const Snake& adv_snake, const unsigned food_x, const unsigned food_y )
|
|
{
|
|
const std::array<Direction,4> classes{
|
|
Direction::UP,
|
|
Direction::DOWN,
|
|
Direction::LEFT,
|
|
Direction::RIGHT,
|
|
};
|
|
|
|
std::array<std::array<float,7>,4> dataset{{
|
|
{0.f,0.f,0.f,0.f,0.f,0.f,0.f},
|
|
{0.f,0.f,0.f,0.f,0.f,0.f,0.f},
|
|
{0.f,0.f,0.f,0.f,0.f,0.f,0.f},
|
|
{0.f,0.f,0.f,0.f,0.f,0.f,0.f}
|
|
}};
|
|
|
|
const std::array<float,7> weights{
|
|
-1.0f, // blocked way
|
|
-0.4f, // dead way
|
|
0.003f, // dead way steps
|
|
0.1f, // food way
|
|
0.4f, // aggressive way
|
|
0.01f, // same direction
|
|
-1.0f // opposite direction
|
|
};
|
|
|
|
this->updateFieldMap( adv_snake, food_x, food_y );
|
|
|
|
for ( size_t i{0}; i<4ul; ++i ) {
|
|
this->collectData( dataset.at(i), classes.at(i), adv_snake, food_x, food_y );
|
|
}
|
|
|
|
// decide
|
|
this->head_direction = this->predictDirection( dataset, weights, classes );
|
|
}
|
|
|
|
|
|
Direction Snake::predictDirection( const std::array<std::array<float,7>,4>& data, const std::array<float,7>& weights, const std::array<Direction,4>& classes ) const noexcept
|
|
{
|
|
float results[]{ 1.f, 1.f, 1.f, 1.f };
|
|
bool keep_current{ false };
|
|
Direction class_label{ this->head_direction };
|
|
|
|
// process data
|
|
for ( size_t i{0}; i<4ul; ++i ) {
|
|
const std::array<float, 7>& d = data.at(i);
|
|
float& r = results[i];
|
|
for ( size_t j{0}; j<7ul; ++j ) {
|
|
r += d.at(j) * weights.at(j);
|
|
}
|
|
}
|
|
|
|
// normalize results (not really a need here...)
|
|
float min{10.f}, max{-10.f};
|
|
for ( const float& r : results ) {
|
|
if ( r < min ) {
|
|
min = r;
|
|
}
|
|
if ( r > max ) {
|
|
max = r;
|
|
}
|
|
}
|
|
if ( max != min ) {
|
|
for ( size_t i{0}; i<4ul; ++i ) {
|
|
results[i] = (results[i]-min) / (max-min);
|
|
}
|
|
} else {
|
|
keep_current |= true;
|
|
for ( size_t i{0}; i<4ul; ++i ) {
|
|
results[i] = 0.f;
|
|
}
|
|
}
|
|
|
|
// choose the best result
|
|
if ( ! keep_current ) {
|
|
max = 0.f;
|
|
for ( size_t i{0}; i<4ul; ++i ) {
|
|
if ( results[i] > max ) {
|
|
class_label = classes.at(i);
|
|
max = results[i];
|
|
} else if ( results[i] == max ) {
|
|
if ( rand()%2 ) {
|
|
class_label = classes.at(i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return class_label;
|
|
}
|
|
|
|
|
|
void Snake::collectData( std::array<float,7>& data, const Direction& direction, const Snake& adv_snake, const unsigned food_x, const unsigned food_y ) const
|
|
{
|
|
unsigned
|
|
blocked_way { 0 },
|
|
dead_way { 0 },
|
|
dead_way_steps { 0 },
|
|
food_way { 0 },
|
|
aggressive_way { 0 },
|
|
same_direction { 0 },
|
|
opposite_direction { 0 };
|
|
|
|
const unsigned head_x{ this->front().x };
|
|
const unsigned head_y{ this->front().y };
|
|
|
|
unsigned x, y;
|
|
|
|
// avoid choosing the opposite direction
|
|
switch ( direction ) {
|
|
|
|
case Direction::UP:
|
|
x = head_x;
|
|
y = head_y-1u;
|
|
if ( this->head_direction == Direction::UP ) {
|
|
same_direction = 1u;
|
|
} else if ( this->head_direction == Direction::DOWN ) {
|
|
opposite_direction = 1u;
|
|
}
|
|
if ( head_y == 0u ) {
|
|
blocked_way = 1u;
|
|
}
|
|
break;
|
|
|
|
case Direction::DOWN:
|
|
x = head_x;
|
|
y = head_y+1u;
|
|
if ( this->head_direction == Direction::DOWN ) {
|
|
same_direction = 1u;
|
|
} else if ( this->head_direction == Direction::UP ) {
|
|
opposite_direction = 1u;
|
|
}
|
|
if ( head_y == 15u ) {
|
|
blocked_way = 1u;
|
|
}
|
|
break;
|
|
|
|
case Direction::LEFT:
|
|
x = head_x-1u;
|
|
y = head_y;
|
|
if ( this->head_direction == Direction::LEFT ) {
|
|
same_direction = 1u;
|
|
} else if ( this->head_direction == Direction::RIGHT ) {
|
|
opposite_direction = 1u;
|
|
}
|
|
if ( head_x == 0u ) {
|
|
blocked_way = 1u;
|
|
}
|
|
break;
|
|
|
|
case Direction::RIGHT:
|
|
x = head_x+1u;
|
|
y = head_y;
|
|
if ( this->head_direction == Direction::RIGHT ) {
|
|
same_direction = 1u;
|
|
} else if ( this->head_direction == Direction::LEFT ) {
|
|
opposite_direction = 1u;
|
|
}
|
|
if ( head_x == 15u ) {
|
|
blocked_way = 1u;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// should be unreachable
|
|
throw("Unexpected direction: "+std::to_string(this->head_direction));
|
|
}
|
|
|
|
|
|
if ( !(blocked_way || opposite_direction) ) {
|
|
|
|
// check snakes
|
|
switch ( this->field_map.at( x ).at( y ).entity ) {
|
|
case Entity::S:
|
|
case Entity::A:
|
|
blocked_way = 1u;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if ( ! blocked_way ) {
|
|
|
|
// check for deadhole
|
|
dead_way_steps = this->isDeadHole( x, y, direction );
|
|
if ( dead_way_steps > 0u ) {
|
|
dead_way = 1u;
|
|
}
|
|
|
|
// check for aggressivity purposes
|
|
switch ( direction ) {
|
|
|
|
case Direction::UP:
|
|
if ( adv_snake.direction() == Direction::LEFT && this->head_direction == Direction::RIGHT ) {
|
|
if ( this->inTileAdv( x+2u, y+1u ) ) {
|
|
aggressive_way = 1u;
|
|
}
|
|
} else if ( adv_snake.direction() == Direction::RIGHT && this->head_direction == Direction::LEFT ) {
|
|
if ( this->inTileAdv( x-2u, y+1u ) ) {
|
|
aggressive_way = 1u;
|
|
}
|
|
} else if ( adv_snake.direction() == this->head_direction ) {
|
|
if ( this->inTileAdv( x+2u, y )
|
|
|| this->inTileAdv( x-2u, y ) ) {
|
|
if ( rand()%this->aggressiveness == 0 ) {
|
|
aggressive_way = 1u;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Direction::DOWN:
|
|
if ( adv_snake.direction() == Direction::LEFT && this->head_direction == Direction::RIGHT ) {
|
|
if ( this->inTileAdv( x+2u, y-1u ) ) {
|
|
aggressive_way = 1u;
|
|
}
|
|
} else if ( adv_snake.direction() == Direction::RIGHT && this->head_direction == Direction::LEFT ) {
|
|
if ( this->inTileAdv( x-2u, y-1u ) ) {
|
|
aggressive_way = 1u;
|
|
}
|
|
} else if ( adv_snake.direction() == this->head_direction ) {
|
|
if ( this->inTileAdv( x+2u, y )
|
|
|| this->inTileAdv( x-2u, y ) ) {
|
|
if ( rand()%this->aggressiveness == 0 ) {
|
|
aggressive_way = 1u;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Direction::LEFT:
|
|
if ( adv_snake.direction() == Direction::UP && this->head_direction == Direction::DOWN ) {
|
|
if ( this->inTileAdv( x+1u, y+2u ) ) {
|
|
aggressive_way = 1u;
|
|
}
|
|
} else if ( adv_snake.direction() == Direction::DOWN && this->head_direction == Direction::UP ) {
|
|
if ( this->inTileAdv( x+1u, y-2u ) ) {
|
|
aggressive_way = 1u;
|
|
}
|
|
} else if ( adv_snake.direction() == this->head_direction ) {
|
|
if ( this->inTileAdv( x, y+2u )
|
|
|| this->inTileAdv( x, y-2u ) ) {
|
|
if ( rand()%this->aggressiveness == 0 ) {
|
|
aggressive_way = 1u;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Direction::RIGHT:
|
|
if ( adv_snake.direction() == Direction::UP && this->head_direction == Direction::DOWN ) {
|
|
if ( this->inTileAdv( x-1u, y+2u ) ) {
|
|
aggressive_way = 1u;
|
|
}
|
|
} else if ( adv_snake.direction() == Direction::DOWN && this->head_direction == Direction::UP ) {
|
|
if ( this->inTileAdv( x-1u, y-2u ) ) {
|
|
aggressive_way = 1u;
|
|
}
|
|
} else if ( adv_snake.direction() == this->head_direction ) {
|
|
if ( this->inTileAdv( x, y+2u )
|
|
|| this->inTileAdv( x, y-2u ) ) {
|
|
if ( rand()%this->aggressiveness == 0 ) {
|
|
aggressive_way = 1u;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// should be unreachable
|
|
throw("Unexpected direction: "+std::to_string(this->head_direction));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if ( !(blocked_way || dead_way) ) {
|
|
|
|
// check for food
|
|
switch ( direction ) {
|
|
|
|
case Direction::UP:
|
|
if ( food_y < head_y ) {
|
|
if ( ! opposite_direction ) {
|
|
food_way = 1u;
|
|
}
|
|
} else if ( food_y == head_y ) {
|
|
if ( food_x > head_x && this->head_direction == Direction::LEFT ) {
|
|
food_way = 1u;
|
|
} else if ( food_x < head_x && this->head_direction == Direction::RIGHT ) {
|
|
food_way = 1u;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Direction::DOWN:
|
|
if ( food_y > head_y ) {
|
|
if ( ! opposite_direction ) {
|
|
food_way = 1u;
|
|
}
|
|
} else if ( food_y == head_y ) {
|
|
if ( food_x > head_x && this->head_direction == Direction::LEFT ) {
|
|
food_way = 1u;
|
|
} else if ( food_x < head_x && this->head_direction == Direction::RIGHT ) {
|
|
food_way = 1u;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Direction::LEFT:
|
|
if ( food_x < head_x ) {
|
|
if ( ! opposite_direction ) {
|
|
food_way = 1u;
|
|
}
|
|
} else if ( food_x == head_x ) {
|
|
if ( food_y > head_y && this->head_direction == Direction::UP ) {
|
|
food_way = 1u;
|
|
} else if ( food_y < head_y && this->head_direction == Direction::DOWN ) {
|
|
food_way = 1u;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Direction::RIGHT:
|
|
if ( food_x > head_x ) {
|
|
if ( ! opposite_direction ) {
|
|
food_way = 1u;
|
|
}
|
|
} else if ( food_x == head_x ) {
|
|
if ( food_y > head_y && this->head_direction == Direction::UP ) {
|
|
food_way = 1u;
|
|
} else if ( food_y < head_y && this->head_direction == Direction::DOWN ) {
|
|
food_way = 1u;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// should be unreachable
|
|
throw("Unexpected direction: "+std::to_string(this->head_direction));
|
|
}
|
|
}
|
|
|
|
|
|
// update data
|
|
data.at(0) = blocked_way;
|
|
data.at(1) = dead_way;
|
|
data.at(2) = dead_way_steps;
|
|
data.at(3) = food_way;
|
|
data.at(4) = aggressive_way;
|
|
data.at(5) = same_direction;
|
|
data.at(6) = opposite_direction;
|
|
}
|
|
|
|
|
|
void Snake::updateFieldMap( const Snake& adv_snake, const unsigned& food_x, const unsigned& food_y ) noexcept
|
|
{
|
|
// reset to default state
|
|
for ( size_t x{0}; x<16ul; ++x ) {
|
|
for ( size_t y{0}; y<16ul; ++y ) {
|
|
Tile& t = this->field_map.at(x).at(y);
|
|
t.entity = Entity::N;
|
|
t.s_index = 0u;
|
|
}
|
|
}
|
|
// update food position
|
|
this->field_map.at(food_x).at(food_y).entity = Entity::F;
|
|
// update self
|
|
unsigned i{ static_cast<unsigned>( this->size() ) };
|
|
for ( auto bp{ this->cbegin() }; bp != this->cend(); ++bp ) {
|
|
Tile& t = this->field_map.at(bp->x).at(bp->y);
|
|
t.entity = Entity::S;
|
|
t.s_index = i;
|
|
--i;
|
|
}
|
|
// update adversary
|
|
i = static_cast<unsigned>( adv_snake.size() );
|
|
for ( const auto& bp : adv_snake ) {
|
|
Tile& t = this->field_map.at(bp.x).at(bp.y);
|
|
t.entity = Entity::A;
|
|
t.s_index = i;
|
|
--i;
|
|
}
|
|
}
|
|
|
|
|
|
bool Snake::inTileAdv(const unsigned x, const unsigned y ) const noexcept
|
|
{
|
|
if ( x < 16 && y < 16 ) {
|
|
switch ( this->field_map.at(x).at(y).entity ) {
|
|
case Entity::A:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Snake::inTileMinusSteps(const unsigned x, const unsigned y, const unsigned steps ) const noexcept
|
|
{
|
|
switch ( this->field_map.at(x).at(y).entity ) {
|
|
case Entity::S:
|
|
case Entity::A:
|
|
if ( this->field_map.at(x).at(y).s_index > steps ) {
|
|
return true;
|
|
}
|
|
return false;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
std::array<unsigned,8> Snake::checkAround( const Direction& direction, const unsigned x, const unsigned y ) const
|
|
{
|
|
std::array<unsigned,8> around{
|
|
0u, 0u, 0u,
|
|
0u, 0u,
|
|
0u, 0u, 0u,
|
|
};
|
|
|
|
std::array<int,8> x_pattern, y_pattern;
|
|
|
|
switch ( direction ) {
|
|
case Direction::UP:
|
|
x_pattern = { -1, 0, 1,
|
|
-1, 1,
|
|
-1, 0, 1 };
|
|
y_pattern = { -1, -1, -1,
|
|
0, 0,
|
|
1, 1, 1 };
|
|
break;
|
|
case Direction::DOWN:
|
|
x_pattern = { 1, 0, -1,
|
|
1, -1,
|
|
1, 0, -1 };
|
|
y_pattern = { 1, 1, 1,
|
|
0, 0,
|
|
-1, -1, -1 };
|
|
break;
|
|
case Direction::LEFT:
|
|
x_pattern = { -1, -1, -1,
|
|
0, 0,
|
|
1, 1, 1 };
|
|
y_pattern = { 1, 0, -1,
|
|
1, -1,
|
|
1, 0, -1 };
|
|
break;
|
|
case Direction::RIGHT:
|
|
x_pattern = { 1, 1, 1,
|
|
0, 0,
|
|
-1, -1, -1 };
|
|
y_pattern = { -1, 0, 1,
|
|
-1, 1,
|
|
-1, 0, 1 };
|
|
break;
|
|
default:
|
|
// should be unreachable
|
|
throw("Unexpected direction: "+std::to_string(direction));
|
|
}
|
|
|
|
unsigned x_, y_;
|
|
for ( size_t i{0}; i<8ul; ++i ) {
|
|
x_ = x;
|
|
x_ += x_pattern.at(i);
|
|
y_ = y;
|
|
y_ += y_pattern.at(i);
|
|
if ( x_ > 15 || y_ > 15 ) {
|
|
around.at(i) = 1u;
|
|
} else {
|
|
switch ( this->field_map.at( x_ ).at( y_ ).entity ) {
|
|
case Entity::S:
|
|
case Entity::A:
|
|
around.at(i) = 2u;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return around;
|
|
}
|
|
|
|
|
|
unsigned Snake::isDeadHole( const unsigned start_x, const unsigned start_y, const Direction& start_direction ) const
|
|
{
|
|
bool result{false}, check{false}, check_clockwise{false};
|
|
Direction direction{ start_direction };
|
|
unsigned steps{ 1 };
|
|
|
|
const auto blocked_around{ this->checkAround( direction, start_x, start_y ) };
|
|
if ( (blocked_around.at(3)>0u && blocked_around.at(4)>0u)
|
|
&& (blocked_around.at(3)>1u || blocked_around.at(4)>1u) ) {
|
|
check |= true;
|
|
} else if ( (blocked_around.at(3)>0u && blocked_around.at(7)>0u)
|
|
&& (blocked_around.at(3)>1u || blocked_around.at(7)>1u) ) {
|
|
check |= true;
|
|
} else if ( (blocked_around.at(4)>0u && blocked_around.at(5)>0u)
|
|
&& (blocked_around.at(4)>1u || blocked_around.at(5)>1u) ) {
|
|
check |= true;
|
|
check_clockwise |= true;
|
|
} else if ( (blocked_around.at(5)>0u && blocked_around.at(7)>0u)
|
|
&& (blocked_around.at(5)>1u || blocked_around.at(7)>1u) ) {
|
|
check |= true;
|
|
} else if ( blocked_around.at(0)>0u && blocked_around.at(1)>0u && blocked_around.at(2)>0u ) {
|
|
check |= true;
|
|
}
|
|
|
|
|
|
if ( check ) {
|
|
|
|
int front_step, front_check, side_check;
|
|
unsigned side, front, aux_side, aux_front;
|
|
const unsigned max_steps{ static_cast<unsigned>(this->size()-1ul) };
|
|
std::vector<std::tuple<unsigned, unsigned>> tried_positions{
|
|
std::make_tuple( start_x, start_y )
|
|
};
|
|
|
|
|
|
const auto change_direction = [&] (const bool clockwise)
|
|
{
|
|
switch ( direction ) {
|
|
case Direction::UP:
|
|
if ( clockwise ) {
|
|
direction = Direction::RIGHT;
|
|
front_check = +1;
|
|
side_check = (check_clockwise) ? +1 : -1;
|
|
} else {
|
|
direction = Direction::LEFT;
|
|
front_check = -1;
|
|
side_check = (check_clockwise) ? -1 : +1;
|
|
}
|
|
break;
|
|
case Direction::DOWN:
|
|
if ( clockwise ) {
|
|
direction = Direction::LEFT;
|
|
front_check = -1;
|
|
side_check = (check_clockwise) ? -1 : +1;
|
|
} else {
|
|
direction = Direction::RIGHT;
|
|
front_check = +1;
|
|
side_check = (check_clockwise) ? +1 : -1;
|
|
}
|
|
break;
|
|
case Direction::LEFT:
|
|
if ( clockwise ) {
|
|
direction = Direction::UP;
|
|
front_check = -1;
|
|
side_check = (check_clockwise) ? +1 : -1;
|
|
} else {
|
|
direction = Direction::DOWN;
|
|
front_check = +1;
|
|
side_check = (check_clockwise) ? -1 : +1;
|
|
}
|
|
break;
|
|
case Direction::RIGHT:
|
|
if ( clockwise ) {
|
|
direction = Direction::DOWN;
|
|
front_check = +1;
|
|
side_check = (check_clockwise) ? -1 : +1;
|
|
} else {
|
|
direction = Direction::UP;
|
|
front_check = -1;
|
|
side_check = (check_clockwise) ? +1 : -1;
|
|
}
|
|
break;
|
|
default:
|
|
// should be unreachable
|
|
throw("Unexpected direction: "+std::to_string(direction));
|
|
}
|
|
// swap front/side
|
|
std::swap( front, side );
|
|
front_step = front_check;
|
|
};
|
|
|
|
|
|
const auto tried_already = [&] (const unsigned side_, const unsigned front_, const bool update) -> bool
|
|
{
|
|
bool tried{ false };
|
|
unsigned x, y;
|
|
switch ( direction ) {
|
|
case Direction::UP:
|
|
case Direction::DOWN:
|
|
x = side_;
|
|
y = front_;
|
|
break;
|
|
case Direction::LEFT:
|
|
case Direction::RIGHT:
|
|
y = side_;
|
|
x = front_;
|
|
break;
|
|
default:
|
|
// should be unreachable
|
|
throw("Unexpected direction: "+std::to_string(direction));
|
|
}
|
|
for ( const auto& position : tried_positions ) {
|
|
if ( std::get<0>(position) == x ) {
|
|
if ( std::get<1>(position) == y ) {
|
|
tried |= true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if ( update ) {
|
|
tried_positions.push_back( std::make_tuple( x, y ) );
|
|
}
|
|
return tried;
|
|
};
|
|
|
|
|
|
const auto tile_blocked = [&] (const unsigned side_, const unsigned front_) -> bool
|
|
{
|
|
bool blocked{ false };
|
|
unsigned x, y, x_, y_;
|
|
switch ( direction ) {
|
|
case Direction::UP:
|
|
case Direction::DOWN:
|
|
x = side_;
|
|
y = front_;
|
|
x_ = side;
|
|
y_ = front;
|
|
break;
|
|
case Direction::LEFT:
|
|
case Direction::RIGHT:
|
|
y = side_;
|
|
x = front_;
|
|
y_ = side;
|
|
x_ = front;
|
|
break;
|
|
default:
|
|
// should be unreachable
|
|
throw("Unexpected direction: "+std::to_string(direction));
|
|
}
|
|
if ( x > 15 || y > 15 ) {
|
|
blocked |= true;
|
|
} else if ( this->inTileMinusSteps( x, y, steps ) ) {
|
|
blocked |= true;
|
|
} else if ( tried_already( side_, front_, false ) ) {
|
|
if ( this->field_map.at( x_ ).at( y_ ).entity == Entity::S ) {
|
|
blocked |= true;
|
|
}
|
|
}
|
|
return blocked;
|
|
};
|
|
|
|
|
|
const auto check_deadhole = [&] ()
|
|
{
|
|
switch ( start_direction ) {
|
|
case Direction::UP:
|
|
front = start_y;
|
|
side = start_x;
|
|
front_check = -1;
|
|
side_check = (check_clockwise) ? +1 : -1;
|
|
break;
|
|
case Direction::DOWN:
|
|
front = start_y;
|
|
side = start_x;
|
|
front_check = +1;
|
|
side_check = (check_clockwise) ? -1 : +1;
|
|
break;
|
|
case Direction::LEFT:
|
|
front = start_x;
|
|
side = start_y;
|
|
front_check = -1;
|
|
side_check = (check_clockwise) ? -1 : +1;
|
|
break;
|
|
case Direction::RIGHT:
|
|
front = start_x;
|
|
side = start_y;
|
|
front_check = +1;
|
|
side_check = (check_clockwise) ? +1 : -1;
|
|
break;
|
|
default:
|
|
// should be unreachable
|
|
throw("Unexpected direction: "+std::to_string(start_direction));
|
|
}
|
|
front_step = front_check;
|
|
|
|
while ( true ) {
|
|
// check the side
|
|
aux_side = side;
|
|
aux_side += side_check;
|
|
if ( !tile_blocked( aux_side, front ) ) {
|
|
// side is free, check for another deadhole
|
|
aux_front = front;
|
|
aux_front += front_check;
|
|
unsigned aux_front_ = front;
|
|
aux_front_ += front_check*(-2);
|
|
if ( tile_blocked( side, aux_front ) && tile_blocked( aux_side, aux_front_ ) ) {
|
|
// may-be deadhole at side, check the opposite
|
|
aux_side = side;
|
|
aux_side += (side_check*(-1));
|
|
if ( !tile_blocked( aux_side, front ) ) {
|
|
// opposite side free
|
|
change_direction( !check_clockwise );
|
|
} else {
|
|
// opposite side blocked too
|
|
change_direction( check_clockwise );
|
|
}
|
|
} else {
|
|
// turn at side
|
|
change_direction( check_clockwise );
|
|
}
|
|
} else {
|
|
// side blocked, check front
|
|
aux_front = front;
|
|
aux_front += front_check;
|
|
if ( tile_blocked( side, aux_front ) ) {
|
|
// front blocked, check opposite side
|
|
aux_side = side;
|
|
aux_side += (side_check*(-1));
|
|
if ( !tile_blocked( aux_side, front ) ) {
|
|
// opposite side free
|
|
change_direction( !check_clockwise );
|
|
} else {
|
|
// opposite side blocked too
|
|
result |= true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
front += front_step;
|
|
|
|
if ( tried_already( side, front, true ) ) {
|
|
result = true;
|
|
break;
|
|
} else {
|
|
++ steps;
|
|
if ( steps >= max_steps ) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
// start checking
|
|
bool aux_result{ false };
|
|
unsigned aux_steps{0}, i{0};
|
|
while ( true ) {
|
|
check_deadhole();
|
|
if ( !result ) {
|
|
steps = 0;
|
|
}
|
|
++ i;
|
|
if ( i == 2u ) {
|
|
break;
|
|
}
|
|
aux_result = result;
|
|
result &= false;
|
|
aux_steps = steps;
|
|
steps = 1u;
|
|
check_clockwise = !check_clockwise;
|
|
direction = start_direction;
|
|
tried_positions.clear();
|
|
}
|
|
|
|
// mean result
|
|
result |= aux_result;
|
|
steps = (steps>aux_steps) ? steps : aux_steps;
|
|
}
|
|
|
|
if ( !result ) {
|
|
steps = 0u;
|
|
}
|
|
return steps;
|
|
}
|