1
7DRLs / Re: Arena (7DRL 2014) [FAILURE]
« on: March 18, 2014, 06:16:35 PM »
Your calculation was spot on koiwai! It's implemented and working beautifully
This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.
trait InputRequest {
fun initialize()
fun execute()
fun isValid(): Boolean
}
fun initialize(initializer: () -> InputRequest): () -> InputRequest {
return {
val request = initializer()
request.initialize()
request
}
}
inputRequests = mapOf(
configuration.moveUp to initialize { MoveRequest(level, player, Direction.North) },
configuration.moveDown to initialize { MoveRequest(level, player, Direction.South) },
configuration.moveLeft to initialize { MoveRequest(level, player, Direction.West) },
configuration.moveRight to initialize { MoveRequest(level, player, Direction.East) },
configuration.toggleWallUp to initialize { ToggleWallRequest(level, player, Direction.North) },
configuration.toggleWallDown to initialize { ToggleWallRequest(level, player, Direction.South) },
configuration.toggleWallLeft to initialize { ToggleWallRequest(level, player, Direction.West) },
configuration.toggleWallRight to initialize { ToggleWallRequest(level, player, Direction.East) },
configuration.shoot to initialize { ShootRequest(level, player, enemies) }
)
override fun update(gameContainer: GameContainer?,
game: StateBasedGame?,
delta: Int) {
if (!renderer.hasAnimationsPlaying()) {
renderer.onAllAnimationsFinished { game!!.enterState(EnemyTurnState.ID) }
enteredInputs(gameContainer!!).filter { it.isValid() }
.forEach { renderer.play(toAnimation(it)) }
}
renderer.update()
}
private fun enteredInputs(gameContainer: GameContainer): List<InputRequest> {
val isKeyPressed = { (key: String) -> inputController.isKeyPressed(gameContainer, key) }
return inputRequests.entrySet()
.filter { isKeyPressed(it.getKey()) }
.map { it.getValue()() }
}
if (!renderer.hasAnimationsPlaying()) {
// ...
}
renderer.onAllAnimationsFinished { game!!.enterState(EnemyTurnState.ID) }
enteredInputs(gameContainer!!).filter { it.isValid() }
.forEach { renderer.play(toAnimation(it)) }
private fun calculateViewArea(): Rectangle {
val playerCoordinates = level.playerCoordinates
val leftTilesAvailable = playerCoordinates.x
val rightTilesAvailable = level.width - playerCoordinates.x
val topTilesAvailable = playerCoordinates.y
val bottomTilesAvailable = level.height - playerCoordinates.y
if (leftTilesAvailable >= 15 && rightTilesAvailable >= 15 &&
topTilesAvailable >= 15 && bottomTilesAvailable >= 15) {
return Rectangle(playerCoordinates.let { Point(it.x - 15, it.y - 15) },
playerCoordinates.let { Point(it.x + 15, it.y + 15) })
}
var numberOfLeftTiles = if (leftTilesAvailable >= 15) 15 else leftTilesAvailable
var numberOfRightTiles = if (rightTilesAvailable >= 15) 15 else rightTilesAvailable
if (numberOfLeftTiles < 15)
numberOfRightTiles += 15 - numberOfLeftTiles
if (numberOfRightTiles < 15)
numberOfLeftTiles += 15 - numberOfRightTiles
var numberOfTopTiles = if (topTilesAvailable >= 15) 15 else topTilesAvailable
var numberOfBottomTiles = if (bottomTilesAvailable >= 15) 15 else bottomTilesAvailable
if (numberOfTopTiles < 15)
numberOfBottomTiles += 15 - numberOfTopTiles
if (numberOfBottomTiles < 15)
numberOfTopTiles += 15 - numberOfBottomTiles
val start = Point(playerCoordinates.x - numberOfLeftTiles,
playerCoordinates.y - numberOfTopTiles)
val end = Point(playerCoordinates.x + numberOfRightTiles,
playerCoordinates.y + numberOfBottomTiles)
return Rectangle(start, end)
}
# Key bindings
moveUp: W
moveLeft: A
moveDown: S
moveRight: D
toggleWallLeft: Left
toggleWallRight: Right
toggleWallUp: Up
toggleWallDown: Down
shoot: Space
class InputController(val configuration: Configuration) {
private val validInputs = listOf(KeyMap[configuration.moveUp]!!,
KeyMap[configuration.moveDown]!!,
KeyMap[configuration.moveLeft]!!,
KeyMap[configuration.moveRight]!!,
KeyMap[configuration.toggleWallUp]!!,
KeyMap[configuration.toggleWallDown]!!,
KeyMap[configuration.toggleWallLeft]!!,
KeyMap[configuration.toggleWallRight]!!,
KeyMap[configuration.shoot]!!)
private var currentState: Input by Delegates.notNull()
/**
* Poll input controllers for list of entered commands.
*
* @param gc Container with current Input state
* @return list of pressed keys
*/
fun update(gc: GameContainer): List<Int> {
currentState = gc.getInput()!!
val userInputs = validInputs filter {
currentState.isKeyDown(it)
}
return userInputs
}
}
private var inputCommands: Map<Int, (Level) -> Unit> by Delegates.notNull()
inputController = InputController(configuration)
inputCommands = mapOf(KeyMap[configuration.moveUp]!! to ::moveUp,
KeyMap[configuration.moveDown]!! to ::moveDown,
KeyMap[configuration.moveLeft]!! to ::moveLeft,
KeyMap[configuration.moveRight]!! to ::moveRight,
KeyMap[configuration.toggleWallUp]!! to ::toggleWallUp,
KeyMap[configuration.toggleWallDown]!! to ::toggleWallDown,
KeyMap[configuration.toggleWallLeft]!! to ::toggleWallLeft,
KeyMap[configuration.toggleWallRight]!! to ::toggleWallRight)
fun toggleWallUp(level: Level) {
val playerCoordinates = level.playerCoordinates
level.toggleFloor(playerCoordinates.let { Point(it.x, it.y - 1) })
}
fun toggleWallDown(level: Level) {
val playerCoordinates = level.playerCoordinates
level.toggleFloor(playerCoordinates.let { Point(it.x, it.y + 1) })
}
fun toggleWallLeft(level: Level) {
val playerCoordinates = level.playerCoordinates
level.toggleFloor(playerCoordinates.let { Point(it.x - 1, it.y) })
}
fun toggleWallRight(level: Level) {
val playerCoordinates = level.playerCoordinates
level.toggleFloor(playerCoordinates.let { Point(it.x + 1, it.y) })
}
class InputController {
private val validInputs = mapOf("W" to Input.KEY_W,
"A" to Input.KEY_A,
"S" to Input.KEY_S,
"D" to Input.KEY_D,
"Up" to Input.KEY_UP,
"Left" to Input.KEY_LEFT,
"Down" to Input.KEY_DOWN,
"Right" to Input.KEY_RIGHT)
private var oldState: Input? = null
private var currentState: Input by Delegates.notNull()
/**
* Poll input controllers for list of entered commands.
*
* @param gc Container with current Input state
* @return list of pressed keys
*/
fun update(gc: GameContainer): List<Pair<String, Int>> {
currentState = gc.getInput()!!
if (oldState == null)
oldState = currentState
val userInputs = validInputs map {
val (key, value) = it
if (currentState.isKeyPressed(value))
key to value
else
null
} filter { it != null } map { it!! }
oldState = currentState
return userInputs
}
}
[Action] = [Key]
MOVE_LEFT = A
MOVE_UP = W
SHOOT = SPACE
...
override fun update(gc: GameContainer?, delta: Int) {
val inputs = inputController.update(gc!!)
logger.debug("Inputs received: $inputs")
inputs.forEach {
if (isMovementKey(it)) {
val requestedDirection = getRequestedDirection(it)
when (requestedDirection) {
Direction.NORTH -> tryMove(Point(playerCoordinates.x, playerCoordinates.y - 1))
Direction.WEST -> tryMove(Point(playerCoordinates.x - 1, playerCoordinates.y))
Direction.SOUTH -> tryMove(Point(playerCoordinates.x, playerCoordinates.y + 1))
Direction.EAST -> tryMove(Point(playerCoordinates.x + 1, playerCoordinates.y))
}
}
}
camera.update(playerCoordinates)
}
private fun isMovementKey(input: Pair<String, Int>): Boolean = listOf("W", "A", "S", "D").containsItem(input.first)
W W F W W W W W W W
W P F F W W W F F W
W F F F F F F F F W
W W W W W W W W W W
[W W F] W W W W W W W
[W P F] F W W W F F W
[W F F] F F F F F F W
W W W W W W W W W W W
W W F W W W W W W W
W F F F W W W F F W
W P F F F F F F F W
W W W W W W W W W W
W W F W W W W W W W
[W F F] F W W W F F W
[W P F] F F F F F F W
[W W W] W W W W W W W W
fun renderGameplay(graphics: Graphics, use: () -> Unit) {
graphics.translate(-cameraCenter.x.toFloat(), -cameraCenter.y.toFloat())
fun renderGameplay(graphics: Graphics, use: () -> Unit) {
graphics.translate(-cameraCenter.x.toFloat(), -cameraCenter.y.toFloat())
use()
graphics.translate(cameraCenter.x.toFloat(), cameraCenter.y.toFloat())
}
class Camera(val configuration: Configuration) {
private val logger = LogManager.getLogger(javaClass<Camera>())!!
private var cameraCenter = Point(0, 0)
val viewportWidth: Int
val viewportHeight: Int
val worldWidth: Int
val worldHeight: Int
private val maximumOffset: Dimension
private val minimumOffset: Dimension;
{
viewportWidth = configuration.viewportWidth
viewportHeight = configuration.viewportHeight
worldWidth = configuration.columns * configuration.tileWidth
worldHeight = configuration.rows * configuration.tileHeight
maximumOffset = Dimension(worldWidth - viewportWidth,
worldHeight - viewportHeight)
minimumOffset = Dimension(0, 0)
}
fun update(centerOn: Point) {
logger.debug("CenterOn coordinates: (${centerOn.x}, ${centerOn.y})")
var cameraX = (centerOn.x * configuration.tileWidth) - viewportWidth / 2
var cameraY = (centerOn.y * configuration.tileHeight) - viewportHeight / 2
if (cameraX > maximumOffset.width)
cameraX = maximumOffset.width
else if (cameraX < minimumOffset.width)
cameraX = minimumOffset.width
if (cameraY > maximumOffset.height)
cameraY = maximumOffset.height
else if (cameraY < minimumOffset.height)
cameraY = minimumOffset.height
cameraCenter = Point(cameraX, cameraY)
logger.debug("Camera Center: (${cameraCenter.x}, ${cameraCenter.y})")
}
/**
* Renders the scene at the current camera position.
*
* @param graphics The graphics device to render the screen
* @param use Function block in which the currently rendered scene is to be filled
*/
fun renderGameplay(graphics: Graphics, use: () -> Unit) {
graphics.translate(-cameraCenter.x.toFloat(), -cameraCenter.y.toFloat())
use()
graphics.translate(cameraCenter.x.toFloat(), cameraCenter.y.toFloat())
}
}
override fun render(gameContainer: GameContainer?, graphics: Graphics?) {
graphics!!.setBackground(Color.white)
camera.renderGameplay(graphics) {
levelSkin.render(level)
}
}
enum class FloorType {
Floor
Wall
Player
}
class Player(configuration: Configuration) {
var maxHitPoints: Int
var hitPoints: Int
var attack: Int
{
maxHitPoints = configuration.hitPoints
hitPoints = configuration.hitPoints
attack = configuration.attack
}
fun takeDamage(amount: Int) {
hitPoints = if (hitPoints - amount < 0) 0 else (hitPoints - amount)
}
fun heal(amount: Int) {
hitPoints += if (hitPoints + amount > maxHitPoints) maxHitPoints else (hitPoints + amount)
}
fun move(gc: GameContainer): Direction? {
val input = gc.getInput()!!
return when {
input.isKeyDown(Input.KEY_W) -> Direction.NORTH
input.isKeyDown(Input.KEY_A) -> Direction.WEST
input.isKeyDown(Input.KEY_S) -> Direction.SOUTH
input.isKeyDown(Input.KEY_D) -> Direction.EAST
else -> null
}
}
}
override fun update(gc: GameContainer?, delta: Int) {
val requestedDirection = player.move(gc!!)
if (requestedDirection != null) {
when (requestedDirection) {
Direction.SOUTH -> tryMove(Point(playerCoordinates.x, playerCoordinates.y + 1))
Direction.NORTH -> tryMove(Point(playerCoordinates.x, playerCoordinates.y - 1))
Direction.EAST -> tryMove(Point(playerCoordinates.x + 1, playerCoordinates.y))
Direction.WEST -> tryMove(Point(playerCoordinates.x - 1, playerCoordinates.y))
}
}
}
private fun tryMove(destination: Point) {
if (destination.x < 0 || destination.y < 0 ||
destination.x >= configuration.width || destination.y >= configuration.height)
return
if (level[destination] == FloorType.Floor) {
level[destination] = FloorType.Player
level[playerCoordinates] = FloorType.Floor
playerCoordinates = destination
}
}
class Skin(val configuration: Configuration) {
private var floorTile: Image by Delegates.notNull()
private var wallTile: Image by Delegates.notNull()
fun loadTiles() {
val skinsDirectory = "assets/${configuration.skin}"
floorTile = Image("${skinsDirectory}/FloorTile.png")
wallTile = Image("${skinsDirectory}/WallTile.png")
}
fun floorTile(): Image {
return floorTile
}
fun wallTile(): Image {
return wallTile
}
fun render(level: Level) {
val tileWidth = configuration.tileWidth
val tileHeight = configuration.tileHeight
for ((point, floor) in level.withIndices()) {
val x = point.x.toFloat() * tileWidth
val y = point.y.toFloat() * tileHeight
when (floor) {
FloorType.Floor -> floorTile().draw(x, y)
FloorType.Wall -> wallTile().draw(x, y)
}
}
}
}
class Level(val dimension: Dimension,
private val level: Array<FloorType>) {
{
val area = dimension.width * dimension.height
Preconditions.checkArgument(area == level.size,
"Level does not conform to the given dimensions; expected ${area}, but was ${level.size}")
}
private val logger = LogManager.getLogger(javaClass<Level>())
private val asciiRepresentation: String by Delegates.lazy {
val asciiLevel = Array<String>(level.size, {
if (level[it] == FloorType.Floor) "." else "#"
})
val asciiRepresentationBuilder = StringBuilder()
for ((index, floor) in asciiLevel.withIndices()) {
if (index % dimension.width == 0)
asciiRepresentationBuilder.append(System.lineSeparator())
asciiRepresentationBuilder.append(floor)
}
asciiRepresentationBuilder.toString()
}
val width = dimension.width
val height = dimension.height
val area = width * height
fun get(point: Point): FloorType {
return get(point.x, point.y)
}
fun get(x: Int, y: Int): FloorType {
return level[x + y * width]
}
fun toString():String {
return asciiRepresentation
}
}
fun Level.iterable(): Iterable<FloorType> {
return object: Iterable<FloorType> {
override fun iterator(): Iterator<FloorType> {
return iterator()
}
}
}
fun Level.iterator(): Iterator<FloorType> {
return object: Iterator<FloorType> {
var x = 0
var y = 0
override fun hasNext(): Boolean {
return (y != height)
}
override fun next(): FloorType {
val next = get(x, y)
x += 1
if (x % width == 0) {
y += 1
x = 0
}
return next
}
}
}
# General options
gameTitle: Arena
# Video
width: 1024
height: 768
fullScreen: false
# Generation
# Which generation scheme to use
levelGenerator: cave
# The height of the generated cave
rows: 20
# The width of the generated cave
columns: 20
# Cave generation
# I'm using a cellular automata described here: http://www.roguebasin.com/index.php?title=Cellular_Automata_Method_for_Generating_Random_Cave-Like_Levels
# The algorithm repeatedly loops over the map, filling in walls depending on a certain set of rules
# These rules can be adjusted to change the appearance of the cave
# Probability of creating a wall on the first pass
wallCreationProbability: 40
# Requirement to remain a wall for every successive pass
neighborsRequiredToRemainAWall: 3
# Requirement to becomea wall for every successive pass
neighborsRequiredToCreateAWall: 5
# Number of passes. Each pass will (hopefully) smooth out the map
numberOfPasses: 10
fun loadConfiguration(filename: String): Configuration {
val configurationPath = Paths.get(filename)!!.toAbsolutePath()!!
Preconditions.checkArgument(Files.exists(configurationPath),
"The configuration file $filename does not exist!")
val configurationMap = Files.newBufferedReader(configurationPath, StandardCharsets.UTF_8).use {
Yaml().load(it) as Map<String, Any?>
}
return Configuration(configurationMap)
}
class Arena(val configuration: Configuration): BasicGame(configuration.gameTitle) {
private val logger = LogManager.getLogger(javaClass<Arena>())!!
private var levelGenerator: Generator by Delegates.notNull()
private var cave: Array<FloorType> by Delegates.notNull()
private var floorTile: Image by Delegates.notNull()
private var wallTile: Image by Delegates.notNull()
override fun init(gc: GameContainer?) {
levelGenerator = when (configuration.levelGenerator) {
"cave" -> CaveGenerator(configuration)
else -> throw IllegalArgumentException("Generation strategy ${configuration.levelGenerator} not recognized")
}
cave = levelGenerator.generate(Dimension(configuration.columns, configuration.rows))
logCave(cave)
floorTile = Image("assets/FloorTile.png")
wallTile = Image("assets/WallTile.png")
}
override fun update(gc: GameContainer?, i: Int) {
// Nothing to do here yet
}
override fun render(gc: GameContainer?, g: Graphics?) {
g!!.setBackground(Color.white)
var row = 20F
for ((index, floor) in cave.withIndices()) {
if (index % 50 == 0)
row += 20F
if (floor == FloorType.Wall)
wallTile.draw(index * 20F, row)
else
floorTile.draw(index * 20F, row)
}
}
private fun logCave(cave: Array<FloorType>) {
val graphicalCave = cave.map { if (it == FloorType.Floor) "." else "#" }
val stringBuilder = StringBuilder()
for ((index, floor) in graphicalCave.withIndices()) {
if (index % 50 == 0)
stringBuilder.append(System.lineSeparator())
stringBuilder.append(floor)
}
logger.debug(stringBuilder.toString())
}
}