Skip to content
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
30 changes: 30 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,53 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
kotlin("jvm") version "1.5.31"
id("io.qameta.allure") version "2.9.6"
id("jacoco")
}

group = "ru.gogolev"
version = "1.0-SNAPSHOT"

val exposedVersion: String by project
val kotestVersion: String by project

allure {
adapter {
autoconfigure.set(true)
}
}

jacoco {
toolVersion = "0.8.7"
}

repositories {
mavenCentral()
}

dependencies {
testImplementation(kotlin("test"))
testImplementation("io.kotest:kotest-runner-junit5:$kotestVersion")
testImplementation("io.kotest:kotest-assertions-core-jvm:$kotestVersion")
testImplementation("io.kotest:kotest-extensions-allure:4.4.3")
testImplementation("org.slf4j:slf4j-simple:1.7.32")

implementation("org.jetbrains.exposed:exposed-core:$exposedVersion")
implementation("org.jetbrains.exposed:exposed-dao:$exposedVersion")
implementation("org.jetbrains.exposed:exposed-jdbc:$exposedVersion")
implementation("org.xerial:sqlite-jdbc:3.36.0.3")
}

tasks.test {
useJUnitPlatform()
ignoreFailures = true
finalizedBy(tasks.allureReport, tasks.jacocoTestReport)
}

tasks.withType<KotlinCompile>() {
kotlinOptions.jvmTarget = "11"
}

tasks.jacocoTestReport {
dependsOn(tasks.test)
}
2 changes: 2 additions & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
kotlin.code.style=official
exposedVersion=0.36.1
kotestVersion=5.0.1
1 change: 0 additions & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@

rootProject.name = "qaOtus-Kotlin"

4 changes: 3 additions & 1 deletion src/main/kotlin/ru/gogolev/seabattle/configuration/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ const val HELP_COUNT = 1
const val SHIPS_LEN_1_COUNT = 8
const val SHIPS_LEN_2_COUNT = 8
const val SHIPS_LEN_3_COUNT = 0
const val SHIPS_LEN_4_COUNT = 2
const val SHIPS_LEN_4_COUNT = 2

const val NEW_GAME = true
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package ru.gogolev.seabattle.configuration

import org.jetbrains.exposed.sql.Database

object DbConnect {
val connect by lazy {
Database.connect("jdbc:sqlite:db/seaBattle.db", driver = "org.sqlite.JDBC")
}
}
27 changes: 27 additions & 0 deletions src/main/kotlin/ru/gogolev/seabattle/entity/ObjectEntity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package ru.gogolev.seabattle.entity

import org.jetbrains.exposed.dao.IntEntity
import org.jetbrains.exposed.dao.IntEntityClass
import org.jetbrains.exposed.dao.id.EntityID
import org.jetbrains.exposed.dao.id.IntIdTable
import ru.gogolev.seabattle.objects.ObjectType

object Objects : IntIdTable() {
val x = integer("x")
val y = integer("y")
val z = integer("z")
val objectType = enumeration("objectType", ObjectType::class)
}

class Object(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<Object>(Objects)

var x by Objects.x
var y by Objects.y
var z by Objects.z
var objectType by Objects.objectType

override fun toString(): String {
return "Object(x=$x, y=$y, z=$z, type=$objectType)"
}
}
18 changes: 16 additions & 2 deletions src/main/kotlin/ru/gogolev/seabattle/fields/BattleField.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package ru.gogolev.seabattle.fields
import ru.gogolev.seabattle.configuration.DEPTH
import ru.gogolev.seabattle.configuration.HEIGHT
import ru.gogolev.seabattle.configuration.WIDTH
import ru.gogolev.seabattle.configuration.NEW_GAME
import ru.gogolev.seabattle.objects.ObjectType
import ru.gogolev.seabattle.utils.DbUtils

fun BattleField.renderLine(y: Int, z: Int): String {
var line = ""
Expand Down Expand Up @@ -37,8 +39,16 @@ fun BattleField.render() {


/** Игровое поле - переопределены операторы get/set для работы с эмуляцией трехмерного массива */
class BattleField {
var field = Array<ObjectType>(DEPTH * WIDTH * HEIGHT, init = { ObjectType.EMPTY })
class BattleField(depth: Int = DEPTH, width: Int = WIDTH, height: Int = HEIGHT) {
private val dbUtils = DbUtils()
private var field = Array(depth * width * height, init = { ObjectType.EMPTY})

fun prepareBattleField(){
val objects = dbUtils.selectAllObjectTypes()
objects.forEach {
field[calculatePosition(Point(it.x, it.y, it.z))] = it.objectType
}
}

operator fun get(x: Int, y: Int, z: Int): ObjectType {
if (checkBoundaries(Point(x, y, z))) {
Expand All @@ -49,6 +59,10 @@ class BattleField {
operator fun set(x: Int, y: Int, z: Int, v: ObjectType) {
if (checkBoundaries(Point(x, y, z)) && isAvailablePoint(Point(x, y, z))) {
field[calculatePosition(Point(x, y, z))] = v
if(NEW_GAME) {
dbUtils.createDb()
dbUtils.insertObjectType(x, y, z, v)
}
} else throw IllegalArgumentException("set incorrect position: $x,$y,$z $v")
}

Expand Down
25 changes: 17 additions & 8 deletions src/main/kotlin/ru/gogolev/seabattle/fields/BattleFieldDelegate.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ package ru.gogolev.seabattle.fields

import ru.gogolev.seabattle.configuration.*
import ru.gogolev.seabattle.objects.*
import ru.gogolev.seabattle.utils.DbFileUtils
import kotlin.random.Random
import kotlin.reflect.KProperty

class BattleFieldDelegate(val gameObjects: MutableList<GameObject>) {
class BattleFieldDelegate(val gameObjects: MutableList<GameObject>, var isNewGame: Boolean = NEW_GAME) {
var battleField: BattleField? = null

// возвращает true, если есть соседи
Expand Down Expand Up @@ -127,17 +128,25 @@ class BattleFieldDelegate(val gameObjects: MutableList<GameObject>) {
}

operator fun getValue(thisRef: Any?, property: KProperty<*>): BattleField {
val dbFileUtils = DbFileUtils()
if (battleField == null) {
//заполнить поле случайным расположением
battleField = BattleField()
//заполняем объектами
println("Mines")
propagateMines()
println("Helps")
propogateHelp()
println("Ships")
propogateShips()
println("Battle field is filled")
if(isNewGame) {
dbFileUtils.createDirectory()
println("Mines")
propagateMines()
println("Helps")
propogateHelp()
println("Ships")
propogateShips()
println("Battle field is filled")
dbFileUtils.backupData()
}
else{
battleField!!.prepareBattleField()
}
}
return battleField!!
}
Expand Down
28 changes: 28 additions & 0 deletions src/main/kotlin/ru/gogolev/seabattle/utils/DbFileUtils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package ru.gogolev.seabattle.utils

import java.nio.file.Files
import kotlin.io.path.Path

class DbFileUtils {
private val main = Path("db/seaBattle.db")
private val backup = Path("db/seaBattle-backup.db")

fun backupData() {
if (Files.exists(main)) {
Files.deleteIfExists(backup)
Files.copy(main, backup)
}
}

fun restoreData() {
if (Files.exists(backup)) {
Files.deleteIfExists(main)
Files.copy(backup, main)
}
}

fun createDirectory() {
val dbPath = Path("db")
if (!Files.isDirectory(dbPath)) Files.createDirectory(dbPath)
}
}
41 changes: 41 additions & 0 deletions src/main/kotlin/ru/gogolev/seabattle/utils/DbUtils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package ru.gogolev.seabattle.utils

import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.transactions.transaction
import ru.gogolev.seabattle.configuration.DbConnect
import ru.gogolev.seabattle.entity.Object
import ru.gogolev.seabattle.entity.Objects
import ru.gogolev.seabattle.objects.ObjectType

class DbUtils {
init {
DbConnect.connect
}

fun createDb(){
transaction {
SchemaUtils.createMissingTablesAndColumns(Objects)
}
}

fun insertObjectType(x: Int, y: Int, z: Int, objectType: ObjectType){
transaction {
Object.new {
this.x = x
this.y = y
this.z = z
this.objectType = objectType
}
}
}

fun selectAllObjectTypes(): ArrayList<Object> {
val arrayList: ArrayList<Object> = arrayListOf()
transaction {
Object.all().forEach {
arrayList += it
}
}
return arrayList
}
}
35 changes: 35 additions & 0 deletions src/test/kotlin/ru/gogolev/test/seabattle/FillingObjectTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package ru.gogolev.test.seabattle

import io.kotest.core.spec.style.FreeSpec
import io.kotest.data.forAll
import io.kotest.data.row
import io.kotest.matchers.shouldBe
import ru.gogolev.seabattle.fields.BattleField
import ru.gogolev.seabattle.fields.Point
import ru.gogolev.seabattle.objects.ObjectType

class FillingObjectTest : FreeSpec() {
init {
"Check filling an Object" - {
val battleField = BattleField()
forAll(
row(0, 0, 0),
row(4, 5, 3),
row(2, 4, 2),
row(7, 7, 7)
) { x, y, z ->
"The object[$x, $y, $z] is available"{
battleField.isAvailablePoint(Point(x, y, z)) shouldBe true
}

"When the object[$x, $y, $z] is filled"{
battleField[x, y, z] = ObjectType.MINE
}

"Then the object[$x, $y, $z] is unavailable"{
battleField.isAvailablePoint(Point(x, y, z)) shouldBe false
}
}
}
}
}
39 changes: 39 additions & 0 deletions src/test/kotlin/ru/gogolev/test/seabattle/StateRecoveryTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package ru.gogolev.test.seabattle

import io.kotest.core.spec.style.FreeSpec
import io.kotest.data.forAll
import io.kotest.data.row
import io.kotest.matchers.shouldBe
import ru.gogolev.seabattle.fields.BattleFieldDelegate
import ru.gogolev.seabattle.fields.Point
import ru.gogolev.seabattle.objects.GameObject
import ru.gogolev.seabattle.objects.ObjectType

class StateRecoveryTest: FreeSpec() {
init{
"Check state recovery" - {
val gameObjects = mutableListOf<GameObject>()
val battleField by BattleFieldDelegate(gameObjects, true)
forAll(
row(0, 0, 0)
) { x, y, z ->
"The object[$x, $y, $z] is available" {
battleField.isAvailablePoint(Point(x, y, z)) shouldBe true
}

"When the object[$x, $y, $z] is filled" {
battleField[x, y, z] = ObjectType.MINE
}

"Then the object[$x, $y, $z] is unavailable" {
battleField.isAvailablePoint(Point(x, y, z)) shouldBe false
}

"And state recovery is working"{
val battleField2 by BattleFieldDelegate(gameObjects, false)
battleField2[x, y, z] shouldBe ObjectType.MINE
}
}
}
}
}