diff --git a/logdoctor/games/snake/snake.cpp b/logdoctor/games/snake/snake.cpp index efc8e01a..10197be1 100644 --- a/logdoctor/games/snake/snake.cpp +++ b/logdoctor/games/snake/snake.cpp @@ -5,6 +5,7 @@ Snake::Snake( const bool& is_adversary ) { this->adversary = is_adversary; + this->will_grow = false; } @@ -18,15 +19,44 @@ QPixmap& Snake::getHeadImage() } -const bool Snake::inTile( const unsigned int& x, const unsigned int& y ) +const bool Snake::inTile( const unsigned int& x, const unsigned int& y , const bool& avoid_tail ) { bool result = false; if ( this->size() > 0 ) { + size_t i = 0, + max = this->size()-1; + if ( !avoid_tail ) { + max ++; + } for ( std::vector::const_iterator bp = this->begin(); bp != this->end(); ++bp ) { if ( bp->x == x && bp->y == y ) { result = true; break; } + i++; + if ( i >= max ) { + break; + } + } + } + return result; +} + +const bool Snake::inTileMinusSteps(const unsigned int& x, const unsigned int& y, const unsigned int& steps ) +{ + bool result = false; + if ( this->size() > 0 ) { + unsigned int + i = 1, + size = this->size(); + for ( std::vector::const_iterator bp = this->begin(); bp != this->end(); ++bp ) {if ( bp->x == x && bp->y == y ) { + result = true; + break; + } + i ++; + if ( i >= size-steps ) { + break; + } } } return result; @@ -42,8 +72,11 @@ const Direction& Snake::direction() return this->head_direction; } - -void Snake::grow( const bool& initial ) +void Snake::willGrow() +{ + this->will_grow = true; +} +void Snake::grow( const bool& is_borning ) { // build from the tail const BodyPart& tail = this->back(); @@ -51,7 +84,7 @@ void Snake::grow( const bool& initial ) unsigned int y = tail.y; const Direction d = tail.direction; const Direction ld = tail.prev_direction; - if ( initial ) { + if ( is_borning ) { // one tile back switch ( d ) { case Direction::UP: @@ -76,13 +109,20 @@ void Snake::grow( const bool& initial ) d, ld, new QGraphicsPixmapItem( (this->adversary) ? this->img_snakeTail_ : this->img_snakeTail ) } ); - this->update( true ); + this->update( nullptr, true ); this->back().update( x, y, d ); } -void Snake::update( const bool& dry ) +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, max_i = this->size()-1; unsigned int new_x, prev_x, new_y, prev_y; @@ -340,289 +380,717 @@ void Snake::update( const bool& dry ) void Snake::move( Snake& snake, const unsigned int& food_x, const unsigned int& food_y ) { - int move_up = 0, - move_down = 0, - move_left = 0, - move_right = 0; + std::vector classes = { + Direction::UP, + Direction::DOWN, + Direction::LEFT, + Direction::RIGHT, + }; + + std::vector> dataset = { + {0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0} + }; + + std::vector weights = { + -1.0, // blocked way + -0.4, // dead way + 0.003, // dead way steps + 0.1, // food way + 0.4, // aggressive way + 0.05, // same direction + -1.0 // opposite direction + }; + + for ( int i=0; i<4; i++ ) { + this->collectData( dataset.at(i), classes.at(i), snake, food_x, food_y ); + } + + // decide + this->head_direction = this->predictDirection( dataset, weights, classes ); +} + + +const Direction Snake::predictDirection( std::vector>& data, std::vector weights , std::vector classes ) +{ + float results[] = { 1.0, 1.0, 1.0, 1.0 }; + bool keep_current = false; + Direction class_label; + + // process data + for ( int i=0; i<4; i++ ) { + std::vector& d = data.at(i); + float& r = results[i]; + for ( int j=0; j<7; j++ ) { + r += d.at(j) * weights.at(j); + } + } + + // normalize results (not really a need here...) + float min=10.0, max=-10.0; + for ( const float& r : results ) { + if ( r < min ) { + min = r; + } + if ( r > max ) { + max = r; + } + } + if ( max != min ) { + for ( int i=0; i<4; i++ ) { + results[i] = (results[i]-min) / (max-min); + } + } else { + keep_current = true; + for ( int i=0; i<4; i++ ) { + results[i] = 0.0; + } + } + + // choose the best result + if ( keep_current ) { + class_label = this->head_direction; + } else { + max = 0; + for ( int i=0; i<4; 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::vector& data, Direction& direction, Snake& adv_snake, const unsigned int& food_x, const unsigned int& food_y ) +{ + unsigned int + blocked_way = 0, + dead_way = 0, + dead_way_steps = 0, + food_way = 0, + aggressive_way = 0, + same_direction = 0, + opposite_direction = 0; const unsigned int head_x = this->front().x; const unsigned int head_y = this->front().y; + unsigned int x, y; + // avoid choosing the opposite direction - switch ( this->head_direction ) { + switch ( direction ) { + case Direction::UP: - move_down -= 500; + x = head_x; + y = head_y-1; + if ( this->head_direction == Direction::UP ) { + same_direction = 1; + } else if ( this->head_direction == Direction::DOWN ) { + opposite_direction = 1; + } + if ( head_y == 0 ) { + blocked_way = 1; + } break; + case Direction::DOWN: - move_up -= 500; + x = head_x; + y = head_y+1; + if ( this->head_direction == Direction::DOWN ) { + same_direction = 1; + } else if ( this->head_direction == Direction::UP ) { + opposite_direction = 1; + } + if ( head_y == 15 ) { + blocked_way = 1; + } break; + case Direction::LEFT: - move_right -= 500; + x = head_x-1; + y = head_y; + if ( this->head_direction == Direction::LEFT ) { + same_direction = 1; + } else if ( this->head_direction == Direction::RIGHT ) { + opposite_direction = 1; + } + if ( head_x == 0 ) { + blocked_way = 1; + } break; + case Direction::RIGHT: - move_left -= 500; + x = head_x+1; + y = head_y; + if ( this->head_direction == Direction::RIGHT ) { + same_direction = 1; + } else if ( this->head_direction == Direction::LEFT ) { + opposite_direction = 1; + } + if ( head_x == 15 ) { + blocked_way = 1; + } break; + default: // should be unreachable throw("Unexpected direction: "+std::to_string(this->head_direction)); } - // check the field's limits - if ( head_y == 0 ) { - move_up -= 500; - } else if ( head_y == 15 ) { - move_down -= 500; - } - if ( head_x == 0 ) { - move_left -= 500; - } else if ( head_x == 15 ) { - move_right -= 500; + + if ( !(blocked_way || opposite_direction) ) { + + // check other snakes + if ( this->inTile( x, y ) ) { + blocked_way = 1; + } else if ( adv_snake.inTile( x, y ) ) { + blocked_way = 1; + } + + if ( ! blocked_way ) { + + // check for deadhole + dead_way_steps = this->isDeadHole( x, y, direction, adv_snake ); + if ( dead_way_steps > 0 ) { + dead_way = 1; + } + + // check for aggressivity purposes + switch ( direction ) { + + case Direction::UP: + if ( adv_snake.direction() == Direction::LEFT && this->head_direction == Direction::RIGHT ) { + if ( adv_snake.inTile( x+2, y+1 ) ) { + aggressive_way = 1; + } + } else if ( adv_snake.direction() == Direction::RIGHT && this->head_direction == Direction::LEFT ) { + if ( adv_snake.inTile( x-2, y+1 ) ) { + aggressive_way = 1; + } + } else if ( adv_snake.direction() == this->head_direction ) { + if ( adv_snake.inTile( x+2, y ) + || adv_snake.inTile( x-2, y ) ) { + if ( !rand()%this->aggressiveness ) { + aggressive_way = 1; + } + } + } + break; + + case Direction::DOWN: + if ( adv_snake.direction() == Direction::LEFT && this->head_direction == Direction::RIGHT ) { + if ( adv_snake.inTile( x+2, y-1 ) ) { + aggressive_way = 1; + } + } else if ( adv_snake.direction() == Direction::RIGHT && this->head_direction == Direction::LEFT ) { + if ( adv_snake.inTile( x-2, y-1 ) ) { + aggressive_way = 1; + } + } else if ( adv_snake.direction() == this->head_direction ) { + if ( adv_snake.inTile( x+2, y ) + || adv_snake.inTile( x-2, y ) ) { + if ( !rand()%this->aggressiveness ) { + aggressive_way = 1; + } + } + } + break; + + case Direction::LEFT: + if ( adv_snake.direction() == Direction::UP && this->head_direction == Direction::DOWN ) { + if ( adv_snake.inTile( x+1, y+2 ) ) { + aggressive_way = 1; + } + } else if ( adv_snake.direction() == Direction::DOWN && this->head_direction == Direction::UP ) { + if ( adv_snake.inTile( x+1, y-2 ) ) { + aggressive_way = 1; + } + } else if ( adv_snake.direction() == this->head_direction ) { + if ( adv_snake.inTile( x, y+2 ) + || adv_snake.inTile( x, y-2 ) ) { + if ( !rand()%this->aggressiveness ) { + aggressive_way = 1; + } + } + } + break; + + case Direction::RIGHT: + if ( adv_snake.direction() == Direction::UP && this->head_direction == Direction::DOWN ) { + if ( adv_snake.inTile( x-1, y+2 ) ) { + aggressive_way = 1; + } + } else if ( adv_snake.direction() == Direction::DOWN && this->head_direction == Direction::UP ) { + if ( adv_snake.inTile( x-1, y-2 ) ) { + aggressive_way = 1; + } + } else if ( adv_snake.direction() == this->head_direction ) { + if ( adv_snake.inTile( x, y+2 ) + || adv_snake.inTile( x, y-2 ) ) { + if ( !rand()%this->aggressiveness ) { + aggressive_way = 1; + } + } + } + break; + + default: + // should be unreachable + throw("Unexpected direction: "+std::to_string(this->head_direction)); + } + } } - // check yourself - if ( ! this->inTile( head_x, head_y-1 ) ) { - move_up += 100; - } else { - move_up -= 100; - } - if ( ! this->inTile( head_x, head_y+1 ) ) { - move_down += 100; - } else { - move_down -= 100; - } - if ( ! this->inTile( head_x-1, head_y ) ) { - move_left += 100; - } else { - move_left -= 100; - } - if ( ! this->inTile( head_x+1, head_y ) ) { - move_right += 100; - } else { - move_right -= 100; - } - // check the adversary - if ( ! snake.inTile( head_x, head_y-1 ) ) { - move_up += 10; - if ( snake.direction() == Direction::LEFT && this->head_direction == Direction::RIGHT ) { - if ( snake.inTile( head_x+2, head_y ) ) { - move_up += 40; - } - } else if ( snake.direction() == Direction::RIGHT && this->head_direction == Direction::LEFT ) { - if ( snake.inTile( head_x-2, head_y ) ) { - move_up += 40; - } - } else if ( snake.direction() == this->head_direction ) { - if ( snake.inTile( head_x+2, head_y-1 ) - || snake.inTile( head_x-2, head_y-1 ) ) { - if ( !rand()%this->aggressiveness ) { - move_up += 40; + if ( !(blocked_way || dead_way) ) { + + // check for food + switch ( direction ) { + + case Direction::UP: + if ( food_y < head_y ) { + if ( ! opposite_direction ) { + food_way = 1; + } + } else if ( food_y == head_y ) { + if ( food_x > head_x && this->head_direction == Direction::LEFT ) { + food_way = 1; + } else if ( food_x < head_x && this->head_direction == Direction::RIGHT ) { + food_way = 1; + } } - } - } - } else { - move_up -= 50; - } - if ( ! snake.inTile( head_x, head_y+1 ) ) { - move_down += 10; - if ( snake.direction() == Direction::LEFT && this->head_direction == Direction::RIGHT ) { - if ( snake.inTile( head_x+2, head_y ) ) { - move_down += 40; - } - } else if ( snake.direction() == Direction::RIGHT && this->head_direction == Direction::LEFT ) { - if ( snake.inTile( head_x-2, head_y ) ) { - move_down += 40; - } - } else if ( snake.direction() == this->head_direction ) { - if ( snake.inTile( head_x+2, head_y+1 ) - || snake.inTile( head_x-2, head_y+1 ) ) { - if ( !rand()%this->aggressiveness ) { - move_down += 40; - } - } - } - } else { - move_down -= 50; - } - if ( ! snake.inTile( head_x-1, head_y ) ) { - move_left += 10; - if ( snake.direction() == Direction::UP && this->head_direction == Direction::DOWN ) { - if ( snake.inTile( head_x, head_y+2 ) ) { - move_left += 40; - } - } else if ( snake.direction() == Direction::DOWN && this->head_direction == Direction::UP ) { - if ( snake.inTile( head_x, head_y-2 ) ) { - move_left += 40; - } - } else if ( snake.direction() == this->head_direction ) { - if ( snake.inTile( head_x-1, head_y+2 ) - || snake.inTile( head_x-1, head_y-2 ) ) { - if ( !rand()%this->aggressiveness ) { - move_left += 40; - } - } - } - } else { - move_left -= 50; - } - if ( ! snake.inTile( head_x+1, head_y ) ) { - move_right += 10; - if ( snake.direction() == Direction::UP && this->head_direction == Direction::DOWN ) { - if ( snake.inTile( head_x, head_y+2 ) ) { - move_right += 40; - } - } else if ( snake.direction() == Direction::DOWN && this->head_direction == Direction::UP ) { - if ( snake.inTile( head_x, head_y-2 ) ) { - move_right += 40; - } - } else if ( snake.direction() == this->head_direction ) { - if ( snake.inTile( head_x+1, head_y+2 ) - || snake.inTile( head_x+1, head_y-2 ) ) { - if ( !rand()%this->aggressiveness ) { - move_right += 40; - } - } - } - } else { - move_right -= 50; - } + break; - // check the food - if ( food_x == head_x ) { - if ( food_y < head_y ) { - move_up += 30; - if ( this->head_direction == Direction::DOWN ) { - move_left += 30; - move_right += 30; - move_up -= 30; - } else if ( this->head_direction == Direction::UP ) { - move_up += 10; - } - } else if ( food_y > head_y ) { - move_down += 30; - if ( this->head_direction == Direction::UP ) { - move_left += 30; - move_right += 30; - move_down -= 30; - } else if ( this->head_direction == Direction::DOWN ) { - move_down += 10; - } - } - } else if ( food_x < head_x ) { - move_left += 30; - if ( food_y == head_y ) { - if ( this->head_direction == Direction::RIGHT ) { - move_up += 30; - move_down += 30; - move_left -= 30; - } else if ( this->head_direction == Direction::LEFT ) { - move_left += 10; - } - } else if ( food_y < head_y ) { - move_up += 30; - if ( this->head_direction == Direction::DOWN ) { - move_up -= 30; - } else if ( this->head_direction == Direction::UP ) { - move_up += 10; - } else if ( this->head_direction == Direction::LEFT ) { - move_left += 10; - } - } else if ( food_y > head_y ) { - move_down += 30; - if ( this->head_direction == Direction::UP ) { - move_down -= 30; - } else if ( this->head_direction == Direction::DOWN ) { - move_down += 10; - } else if ( this->head_direction == Direction::LEFT ) { - move_left += 10; - } - } - } else if ( food_x > head_x ) { - move_right += 30; - if ( food_y == head_y ) { - if ( this->head_direction == Direction::LEFT ) { - move_up += 30; - move_down += 30; - move_right -= 30; - } else if ( this->head_direction == Direction::RIGHT ) { - move_right += 10; - } - } else if ( food_y < head_y ) { - move_up += 30; - if ( this->head_direction == Direction::DOWN ) { - move_up -= 30; - } else if ( this->head_direction == Direction::UP ) { - move_up += 10; - } else if ( this->head_direction == Direction::RIGHT ) { - move_right += 10; - } - } else if ( food_y > head_y ) { - move_down += 30; - if ( this->head_direction == Direction::UP ) { - move_down -= 30; - } else if ( this->head_direction == Direction::DOWN ) { - move_down += 10; - } else if ( this->head_direction == Direction::RIGHT ) { - move_right += 10; - } + case Direction::DOWN: + if ( food_y > head_y ) { + if ( ! opposite_direction ) { + food_way = 1; + } + } else if ( food_y == head_y ) { + if ( food_x > head_x && this->head_direction == Direction::LEFT ) { + food_way = 1; + } else if ( food_x < head_x && this->head_direction == Direction::RIGHT ) { + food_way = 1; + } + } + break; + + case Direction::LEFT: + if ( food_x < head_x ) { + if ( ! opposite_direction ) { + food_way = 1; + } + } else if ( food_x == head_x ) { + if ( food_y > head_y && this->head_direction == Direction::UP ) { + food_way = 1; + } else if ( food_y < head_y && this->head_direction == Direction::DOWN ) { + food_way = 1; + } + } + break; + + case Direction::RIGHT: + if ( food_x > head_x ) { + if ( ! opposite_direction ) { + food_way = 1; + } + } else if ( food_x == head_x ) { + if ( food_y > head_y && this->head_direction == Direction::UP ) { + food_way = 1; + } else if ( food_y < head_y && this->head_direction == Direction::DOWN ) { + food_way = 1; + } + } + break; + + default: + // should be unreachable + throw("Unexpected direction: "+std::to_string(this->head_direction)); } } - // decide - int max = -1000; - Direction choice; - if ( move_up > max || (move_up == max && rand()%2) ) { - bool ok = true; - for ( int y=head_y-1; y>=0; y-- ) { - if ( this->inTile( head_x, y ) ) { - ok = false; - break; - } - } - if ( ok ) { - max = move_up; - choice = Direction::UP; - } - } - if ( move_down > max || (move_down == max && rand()%2) ) { - bool ok = true; - for ( int y=head_y+1; y<16; y++ ) { - if ( this->inTile( head_x, y ) ) { - ok = false; - break; - } - } - if ( ok ) { - max = move_down; - choice = Direction::DOWN; - } - } - if ( move_left > max || (move_left == max && rand()%2) ) { - bool ok = true; - for ( int x=head_x-1; x>=0; x-- ) { - if ( this->inTile( x, head_y ) ) { - ok = false; - break; - } - } - if ( ok ) { - max = move_left; - choice = Direction::LEFT; - } - } - if ( move_right > max || (move_right == max && rand()%2) ) { - bool ok = true; - for ( int x=head_x+1; x<16; x++ ) { - if ( this->inTile( x, head_y ) ) { - ok = false; - break; - } - } - if ( ok ) { - choice = Direction::RIGHT; - } - } - // apply the move - this->head_direction = choice; + // 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; +} + + +const std::vector Snake::checkAround( const Direction& direction, Snake& adv_snake, const unsigned int& x, const unsigned int& y ) +{ + std::vector around = { + 0, 0, 0, + 0, 0, + 0, 0, 0, + }; + + std::vector 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 int x_, y_; + for ( int i=0; i<8; i++ ) { + x_ = x; + x_ += x_pattern.at(i); + y_ = y; + y_ += y_pattern.at(i); + if ( x_ > 15 || y_ > 15 ) { + around.at(i) = 1; + } else if ( this->inTile( x_, y_ ) ) { + around.at(i) = 2; + } else if ( adv_snake.inTile( x_, y_ ) ) { + around.at(i) = 2; + } + } + + return around; +} + + +const unsigned int Snake::isDeadHole( const unsigned int& start_x, const unsigned int& start_y, Direction start_direction, Snake& adv_snake , const bool& inverse ) +{ + bool result=false, check=false, check_clockwise=false; + int front_step, front_check, side_check; + unsigned int steps=1, side, front; + Direction direction = start_direction; + + const auto blocked_around = this->checkAround( direction, adv_snake, start_x, start_y ); + if ( (blocked_around.at(3)>0 && blocked_around.at(4)>0) + && (blocked_around.at(3)>1 || blocked_around.at(4)>1) ) { + check = true; + } else if ( (blocked_around.at(3)>0 && blocked_around.at(7)>0) + && (blocked_around.at(3)>1 || blocked_around.at(7)>1) ) { + check = true; + } else if ( (blocked_around.at(4)>0 && blocked_around.at(5)>0) + && (blocked_around.at(4)>1 || blocked_around.at(5)>1) ) { + check = true; + check_clockwise = true; + } else if ( blocked_around.at(0)>0 && blocked_around.at(3)>0 ) { + check = true; + } else if ( blocked_around.at(2)>0 && blocked_around.at(4)>0 ) { + check = true; + check_clockwise = true; + } + + + if ( check ) { + + unsigned int aux_side, aux_front; + const unsigned int max_steps = this->size()-1; + std::vector> tried_positions = { + std::make_tuple( start_x, start_y ) + }; + + std::function change_direction; + std::function tried_already; + std::function tile_blocked; + std::function check_deadhole; + + + 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 + front = front ^ side; + side = front ^ side; + front = front ^ side; + front_step = front_check; + }; + + + tried_already = [&](const unsigned int& side_, const unsigned int& front_, const bool& update) { + bool tried = false; + unsigned int 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; + }; + + + tile_blocked = [&](const unsigned int& side_, const unsigned int& front_) { + bool blocked = false; + unsigned int 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 ( adv_snake.inTileMinusSteps( x, y, steps ) ) { + blocked = true; + } else if ( this->inTileMinusSteps( x, y, steps ) ) { + blocked = true; + } else { + if ( tried_already( side_, front_, false ) ) { + if ( this->inTile( x_, y_ ) ) { + blocked = true; + } + } + } + return blocked; + }; + + + 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 int 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 int aux_steps=0, i=0; + while ( true ) { + check_deadhole(); + i ++; + if ( i >= 2 || !result ) { + break; + } else { + aux_result = result; + result = false; + aux_steps = steps; + steps = 1; + check_clockwise = !check_clockwise; + direction = start_direction; + tried_positions.clear(); + } + } + + // choose the best result (namely, the negative one or the positive one with more steps) + if ( result ) { + if ( aux_result ) { + if ( aux_steps > steps ) { + steps = aux_steps; + } + } else { + result = aux_result; + } + } + } + + if ( !result ) { + steps = 0; + } + return steps; } diff --git a/logdoctor/games/snake/snake.h b/logdoctor/games/snake/snake.h index 0fb83cac..91676c32 100644 --- a/logdoctor/games/snake/snake.h +++ b/logdoctor/games/snake/snake.h @@ -4,6 +4,7 @@ #include #include +#include #include @@ -42,21 +43,24 @@ public: QPixmap& getHeadImage(); //! Checks whether is there a part of the snake in the given position - const bool inTile( const unsigned int& x, const unsigned int& y ); + const bool inTile( const unsigned int& x, const unsigned int& y, const bool& avoid_tail=true ); - //! Sets the new direction of the head + // [AI] As inTile(), without counting as much trailing BodyParts as the number of steps + const bool inTileMinusSteps( const unsigned int& x, const unsigned int& y, const unsigned int& steps ); + + //! Sets the new direction (of the head) void setDirection( const Direction new_direction ); - //! Returns the current direction + //! Returns the current direction (of the head) const Direction& direction(); - //! Increases the length of the body of the snake of 1 part - void grow( const bool& initial=false ); + //! Updates the position and direction of the entire snake + void update( QGraphicsScene* field_scene=nullptr, const bool& dry=false, const bool& is_borning=false ); - //! Update the position and direction of the entire snake - void update( const bool& dry=false ); + // Schedules to grow the snake on the next update + void willGrow(); - //! Chooses a new direction for the snake (only used by the AI) + // [AI] Chooses a new direction for the snake void move( Snake& snake, const unsigned int& food_x, const unsigned int& food_y ); @@ -74,11 +78,28 @@ private: Direction head_direction; + bool will_grow; + + //! Increases the length of the body of the snake of 1 part + void grow( const bool& is_borning ); + bool adversary; //// ADVERSARY //// const unsigned int aggressiveness = 10 - (rand()%9); + + // [AI] Checks which of the surrounding positions are blocked + const std::vector checkAround( const Direction& direction, Snake& adv_snake, const unsigned int& x, const unsigned int& y ); + + // [AI] Checks if a direction is a closed path and should be avoided + const unsigned int isDeadHole( const unsigned int& start_x, const unsigned int& start_y, Direction start_direction, Snake& adv_snake, const bool& inverse=false ); + + // [AI] Collects data about the possible movements + void collectData( std::vector& data, Direction& direction, Snake& adv_snake, const unsigned int& food_x, const unsigned int& food_y ); + + // [AI] Processes the collected data to predict the best movement + const Direction predictDirection( std::vector>& data, std::vector weights, std::vector classes ); }; #endif // SNAKE_H