Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Searcher #3

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions src/main/kotlin/ru/spbstu/terrai/core/Condition.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package ru.spbstu.terrai.core

data class Condition(val items: List<Item>, val exitReached: Boolean) {
constructor(): this(false)
data class Condition(val items: List<Item>, val exitReached: Boolean, val exitFind: Boolean) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Не find, а found

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Кроме этого, поскольку данная штука нужна конкретному игроку, корректнее запоминать её в нём, не меняя внутренние классы игры

constructor(): this(false, false)

constructor(exitReached: Boolean): this(emptyList(), exitReached)
constructor(exitReached: Boolean, exitFind: Boolean): this(emptyList(), exitReached, exitFind)

val hasTreasure get() = Treasure in items


}
2 changes: 2 additions & 0 deletions src/main/kotlin/ru/spbstu/terrai/lab/Controller.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class Controller(private val lab: Labyrinth, private val player: Player) {
fun makeMove(): GameResult {
if (playerCondition.exitReached) return GameResult(moves, exitReached = true)
val move = player.getNextMove()

val moveResult = when (move) {
WaitMove -> {
MoveResult(lab[playerLocation], playerCondition, true, "Nothing changes")
Expand Down Expand Up @@ -67,6 +68,7 @@ class Controller(private val lab: Labyrinth, private val player: Player) {
true to "Exit reached, you won"
}
else {
playerCondition = playerCondition.copy(exitFind = true)
true to "Exit reached but you do not have a treasure"
}
}
Expand Down
256 changes: 256 additions & 0 deletions src/main/kotlin/ru/spbstu/terrai/players/samples/Searcher.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
package ru.spbstu.terrai.players.samples

/**попытка модернизации класса DummySeracher'a
* Выход - единственное, в существовании чего можно не сомневаться.
* Created by Numay on 15.04.2017.
*/

import ru.spbstu.terrai.core.*
import java.util.*

class Searcher : AbstractPlayer(){//класс-реализация абстрактного игрока-ИИ

private lateinit var currentLocation: Location //хранит положение на карте

private val roomMap = mutableMapOf<Location, Room>()//карта хранящая исследованные ботом клетки

override fun setStartLocationAndSize(location: Location, width: Int, height: Int) {
super.setStartLocationAndSize(location, width, height)
currentLocation = location
roomMap[currentLocation] = Entrance//стартовая точка - вход
}

private var lastMove: Move = WaitMove

private val decisions = mutableListOf<Direction>()//список, хранящий сделанные ходы

private val pathToExit = mutableListOf<Direction>()//список, хранящий путь от ямы до выхода

private var wormholes = 0 //считает количество уже пройденных червоточин
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Далее по коду получается, что это очень хакерское поле, и название его не совсем точно


private var maxholes = 0 //хранит количество червоточин на карте
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

По Code Style -- maxHoles (и ниже такие места ещё попадаются)


private var pathWasFinded = false //найден ли путь от вормхолла до выхода
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тоже Found

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В общем и целом, флагов у вас накопилось уже столько, что стоило их все убрать и завести единую переменную-перечисление "Состояние". Его и использовать


private var treasureFinded = false
//Алгоритм, возвращающий бота к вормхоллу выхода
private var backInTheFirstWormholeFlag = false
//Режим связывания всех существующих вормхоллов с их комнатами
private var bindingFlag = false
private var pathTemp: MutableList<Direction>? = mutableListOf<Direction>()
//хранит последний результат движения
private var lastResult: MoveResult?=null //копия последней полученной информации о состоянии
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Здесь в Котлине лучше так:

private lateinit var lastResult: MoveResult

Дело в том, что по факту оно всё равно используется как not-null, а null бывает только в самом начале работы

private var loopwWh = false //завис ли бот в цикле
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Это название в принципе неудачное


//функция управления движением//работает нормально//пока
override fun getNextMove(): Move {//если есть любая клетка, в которой бот еще не был, то он идет туда

var rng = Random();//для выбора направления
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Генератор случайных чисел лучше определять на верхнем уровне, не создавая его каждый раз заново

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Точки с запятой в Котлине почти никогда не нужны. Также важно употреблять val везде, где это возможно, заменяя var (IDE тут подсказывает)

var toUnknownDirection: Direction?

//Особые ситуации

if(wormholes>11){//попытка прервать ушедшего в цикл бота
//не очень
if(loopwWh){//для совсем печальных случаев
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Такие вот ветки программистами называются "подхак". Лучше их избегать.

bindingFlag = false
pathWasFinded = false
}


var step: Direction
if(!lastResult!!.successful || lastResult?.room is Wormhole){
step=Direction.values().first{it + currentLocation !in roomMap}
decisions+=step
lastMove=WalkMove(step)
return lastMove
}
step=decisions.last()
wormholes=0
roomMap.clear()
lastMove=WalkMove(step.turnBack())
loopwWh = true
return lastMove
}

if(backInTheFirstWormholeFlag){//бот нашел клад и должен вернуться ко входу
var step: Direction
if (wormholes!=1){
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Почему вдруг именно 1? В чём специальность этого значения?

if(lastResult?.room !is Wormhole && lastResult!!.successful){
step=decisions.last()
decisions.removeAt(decisions.size - 1)
return WalkMove(step.turnBack())
}
if(!lastResult!!.successful){
step=Direction.values().first{it + currentLocation in roomMap}
return WalkMove(step)
}
}
val tempmove = followThePath()
if(tempmove == null){
return WaitMove
}
return followThePath()!!
}
if(bindingFlag){//бот считает дырки
var tempMove=searching()
if(tempMove!=null) {
lastMove = tempMove
return lastMove
}
bindingFlag=false
}

//стандартный случай
if(Direction.values().firstOrNull{it + currentLocation !in roomMap} != null) {
do {//направление выбирается случайно, а не по списку
toUnknownDirection = Direction.values().firstOrNull { (it.ordinal == rng.nextInt(4) + 0) && (it + currentLocation !in roomMap)}
} while (toUnknownDirection == null)//это позволит решить лабиринты типа 5го, при условно бесконечном количестве ходов, конечно
}
else toUnknownDirection=null
lastMove = if (toUnknownDirection != null) {
decisions += toUnknownDirection
WalkMove(toUnknownDirection)
}
else {//если нет, то бот идет назад
val lastDecision = decisions.lastOrNull()
if (lastDecision != null) {
decisions.removeAt(decisions.size - 1)
val backDirection = lastDecision.turnBack()
WalkMove(backDirection)
}
else {
WaitMove
}
}

return lastMove
}

//создает копию пути от вормхолла до выхода
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Скорее, видимо, не до выхода, а до входа в текущую комнату

private fun getPathToExit (): Move?{ //ну и возвращает бота к вормхоллу откуда он шел
var lastStep: Direction
lastStep = decisions.lastOrNull() ?: return null
decisions.removeAt(decisions.size - 1)
pathToExit+=lastStep
return WalkMove(lastStep.turnBack())
}
//повторяет путь от вормхолла до выхода, если не находит выход в конце - возвращается к ближайшему вормхоллу
//возвращает null, если выход найден
private fun followThePath (): Move?{
var step : Direction
//бот следует по запомненному пути к выходу
if(lastResult?.room is Wormhole){//свалился в новый вормхолл -> скопировал маршрут заново
pathTemp = mutableListOf<Direction>()
pathTemp?.addAll(pathToExit)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Не очень понял смысл этой ветки. Можно обсудить при встрече

}
if(!lastResult!!.successful && decisions.size>0)//уперся в стену -> возвратился к вормхоллу
pathTemp = null
step = pathTemp?.firstOrNull() ?:
if (lastResult?.room is Exit) //прошел путь
return null //нашел выход
else {//не нашел выход -> возвращается к точке входа в комнату
step=decisions.last()
decisions.removeAt(decisions.size - 1)
return WalkMove(step.turnBack())
}
pathTemp?.removeAt(0)
decisions += step
return WalkMove(step)
}
//процесс поиска количества вормхоллов для оптимизации поиска
//возвращает null, когда количество найдено
private fun searching(): Move?{
//здесь ищется PathToExit
if(!pathWasFinded) {
if (wormholes > 0) {
var newmove = getPathToExit()
if (newmove != null)
return newmove
}
pathWasFinded=true
roomMap.clear()//считаем с самого начала, смысла в старой карте больше нет
wormholes=2;//с этого момента начинается анализ карты, имеет смысл также сбросить счетчик
//но т.к. мы рассматриваем миры в привязке к вормхоллам, то минимальное количество дырок = 2
}
//пропускаем одну комнату, так как, очевидно, что оттуда вход не найти
if(wormholes==2 || wormholes>11){

var step: Direction
if(!lastResult!!.successful || lastResult?.room is Wormhole){
step=Direction.values().first{it + currentLocation !in roomMap}
decisions+=step
return WalkMove(step)
}
step=decisions.last()
if(wormholes>11){
wormholes=2
roomMap.clear()
}
return WalkMove(step.turnBack())
}

var lastmove = followThePath()
if (lastmove == null){
maxholes = wormholes-1//число дырок
return null
}
return lastmove
}
//функция, определяющая алгоритм поведения бота
override fun setMoveResult(result: MoveResult) {
val newLocation = (lastMove as? WalkMove)?.let { it.direction + currentLocation } ?: currentLocation//вычисляет местоположение после хода
val room = result.room//объект, с которым столкнулся игрок
lastResult=result
roomMap[newLocation] = room//запись объекта на карту
if (result.successful) {//если двигался
when(room) {
is Wormhole -> {//если червоточина
if(result.condition.exitFind && !pathWasFinded){//случай, когда бот сначала нашел выход, а потом дырку
pathToExit += decisions
bindingFlag=true//флаг, запускающий алгоритм связывания вормхоллов
}
decisions.clear()//ходы можно выбрасывать
wormholes++
if(maxholes!=0){//если мы уже знаем сколько всего у нас вормхолов
if(wormholes>maxholes)
wormholes=1;//номер комнаты, по сути
}
currentLocation = Location(wormholes * 100, wormholes * 100)
roomMap[currentLocation] = room
}
is Exit -> {//выход, очевидно
if (!pathWasFinded && wormholes > 0)//когда из червоточины нашел выход
bindingFlag=true
}
else -> {//если клетка пустая
currentLocation = newLocation
if(!treasureFinded && result.condition.hasTreasure){//...после того, как бот в ней пошарил
treasureFinded=true
if(result.condition.exitFind){
if(maxholes>0) {//если найден и выход, и дырки
backInTheFirstWormholeFlag =true
}
if(wormholes==0) {//есть веростность, что бот уже прошел мимо выхода, нужно сжигать карту
roomMap.clear()
decisions.clear()
}
}
}
}
}
}
else {
decisions.removeAt(decisions.size - 1)//если стоит на месте
}
}

}


fun main(args: Array<String>) {

var test = SearcherTest()
test.testLab7()

}
44 changes: 44 additions & 0 deletions src/test/kotlin/ru/spbstu/terrai/players/samples/SearcherTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package ru.spbstu.terrai.players.samples

import org.junit.Test
import ru.spbstu.terrai.lab.Controller

class SearcherTest : AbstractPlayerTest() {

override fun createPlayer() = Searcher()

@Test
fun testLab1() {
doTestLab("labyrinths/lab1.txt", Controller.GameResult(15, exitReached = true))
}

@Test
fun testLab2() {
doTestLab("labyrinths/lab2.txt", Controller.GameResult(48, exitReached = true))
}

@Test
fun testLab3() {
doTestLab("labyrinths/lab3.txt", Controller.GameResult(15, exitReached = true))
}

@Test
fun testLab4() {
doTestLab("labyrinths/lab4.txt", Controller.GameResult(43, exitReached = true))
}

@Test
fun testLab5() {
doTestLab("labyrinths/lab5.txt", Controller.GameResult(5, exitReached = true))
}

@Test
fun testLab6() {
doTestLab("labyrinths/lab6.txt", Controller.GameResult(100, exitReached = true))
}

@Test
fun testLab7() {
doTestLab("labyrinths/lab7.txt", Controller.GameResult(100, exitReached = true))
}
}