Show Posts

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.


Messages - SoulBeaver

Pages: [1] 2
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 :)

2
7DRLs / Re: Arena (7DRL 2014) [FAILURE]
« on: March 18, 2014, 05:49:43 PM »
Hi koiwai,

thanks for your input! I meant to refactor the gargantuan view area method, but since it worked and I didn't really want to change something that worked. Your implementation looks a lot prettier though, and a post-7drl refactoring should be fine, so I'll give it a try :)

As for why I chose Kotlin, there are a few reasons. Java is much too verbose and, in my opinion, not suited for game development. I might have used Java 8, but it's not really here yet and I didn't feel like imposing Java 8 as that might break my platform independence (can Linux do Java 8?)

I could've gone with C++11, as that is another of my stronger languages, but, for as awesome I consider the language to be, it's not as easy to just quickly bash out code as it is in a language such as Java or Kotlin. Granted, C++ is magnitudes more performant, but I felt that a 2D roguelike doesn't need that sort of engine.

While I'm not as experienced in it, Python or Ruby could have suited this project as well. I'm better writing Python than Ruby, so I'll talk about that. The discomfort of not having mastered the language was most definitely a factor. It was a seven day roguelike and I didn't want to gamble. I'm also a fan of static typing. Nevertheless, Python inspired a few snippets in my code, most notably the Configuration intialization by map which I think is a terrific feature in Python. It might've been nice to had a few other functionalities from Python available- maybe next time.

Finally, I could've programmed in F#, my one functional dialect. Quite frankly, this is the language I'm least competent in. I love the immutability, the function composition and piping the output of one function to the next. You can see quite a few places in Kotlin where I do exactly that. As soon as I'm competent enough I'll give a 7DRL with F# a try and see how that pans out :)

Kotlin is a young language, but the functional features it provides hits a sweet spot for me. The (almost) seamless interop with all java libraries means I still get the full technology stack, which is great. Plus, it boasts free-floating functions which is a must. I immensely dislike the trend of shoehorning everything into a class when it doesn't need to be. That is one of its greatest assets in my opinion. Plus, it's statically typed, and I like that. No gripes with Python or the other dynamic languages, just a personal preference.

I'm not entirely satisfied with Kotlin though. The C-style syntax is pretty ugly in my opinion and there are large issues to address- I can't get the bloody thing to work outside of the IntelliJ IDE which is horrible if you're trying to show the game to other people. I'm still looking for a language that will really get me to say "Oooooooooooh look at that sexy language." Elixir and Fancy are exceptionally close, but neither are suited for game development.

tl;dr Kotlin is statically typed, has functional idioms and almost seamless Java interop. It's not perfect, still looking for a better language (e.g. Elixir or Fancy), but that's primarily why I chose it :)

3
7DRLs / Re: Arena (7DRL 2014)
« on: March 18, 2014, 08:25:57 AM »
*sigh* I'm sorry, this project was a failure. I went ahead and rewrote the game into states to allow for correct input handling and animation that was homogenous for both players and enemies. On Thursday I received work for my research project which kept me busy well into Sunday, resulting in little to no further updates on the game. It was going well, I suppose, but this project is now officially a failure.

I completed development yesterday, using an illegal eighth day, to boot the game back into a playable state. Here's the changes:

1. States

There is now a turn-based system baked into the game. We transition from the Main Menu into the PlayerTurnState. We remain there until the user enters a command at which point we execute that command and transition into the EnemyState.

2. Animation

Every Input is called an InputRequest. It contains all information necessary to create an animation out of it, so my toAnimation(InputRequest) method does just that. From there, an AnimationPlayer queues and plays all pending animations remaining for a given state. For example, if the player moves left, then a MoveRequest is created, turned into a MoveAnimation and then played. After it's done, the EnemyTurnState is invoked.

I like this solution because it uses what I believe is called Continuation Passing Style. It's a functional idiom whose counterpart can be best explained with the Command Pattern. The Command Pattern is just a wrapper because free-floating functions usually aren't allowed and thus everything must be wrapped in a class.

This is all a little theoretical, here is some code:

We have our InputRequests:

Code: [Select]
trait InputRequest {
    fun initialize()
    fun execute()
    fun isValid(): Boolean
}

fun initialize(initializer: () -> InputRequest): () -> InputRequest {
    return {
        val request = initializer()
        request.initialize()
        request
    }
}

Which is used to initialize my keymap:

Code: [Select]
    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) }
        )

and then acted on whenever an input is received:

Code: [Select]
    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()() }
    }

In this last snippet you can see how the code is glued together. It's pretty terse, so I'll take it step-by-step.

Code: [Select]
    if (!renderer.hasAnimationsPlaying()) {
            // ...
        }

A State is split into two sub-states:  accepting input and animating. If we're not animating, we're accepting input and vice-versa. When we enter a state, then we first wait for input, and need to make sure that all further calls are not accepting input.

Code: [Select]
renderer.onAllAnimationsFinished { game!!.enterState(EnemyTurnState.ID) }
onAllAnimationsFinished is a function which accepts a function- Higher Order Functions is the concept. I'm telling the renderer what to do when all animations have finished. This inversion of control, my PlayerTurnState telling the Renderer to do, not letting the Renderer decide what happens, is called Continuation Passing Style. A difficult name for a simple concept :)

Code: [Select]
enteredInputs(gameContainer!!).filter  { it.isValid() }
                                          .forEach { renderer.play(toAnimation(it)) }

I poll the keyboard for all relevant inputs (the keys in the map) and make sure that all entered commands are valid. For all valid commands I queue an animation to be played.

That's it :) This lets me handle a multitude of commands- right now we have shooting, moving and toggling walls and floors. I wasn't able to implement enemies yet, even though they ARE in the code. Well, a very primitive version of them.

3. Shooting

Myes, that's possible now. Not much to say, it's very basic and there are no enemies yet.

4. Conclusion

I learned a lot. As I said in the beginning, this was my first roguelike and, to be honest, my first real attempt at programming a game. No other game came as far as this one- even though this one didn't even come that far! However, for as ashamed as I am in the bitter defeat I've suffered here, it was great fun. I'll participate again and with all of this new knowledge and these new tools, I'll be able to create a better game in less time. Fate permitting, I won't be pulled off the contest again, but who knows :)

As usual, all code can be found on Github:  https://github.com/SoulBeaver/7DRL

That's my time, take care guys! Congratulations to all of you that finished, what great entries! :)

4
7DRLs / Re: Arena (7DRL 2014)
« on: March 11, 2014, 07:51:00 PM »
Oof, today was a lackluster day. I didn't accomplish everything I wanted to do. There were a few bottlenecks that prevented extending my code with the desired features. The first was a performance issue, which I solved in my previous post. The next one was a little harder to reason about.

Currently, the block moves at a speed of 20px per frame. That's breakneck speed! It's much too difficult to control the player, so I decided I would implement some animation between each movement. Even though it's "just a blue block", the Player will one day feature animations and I should factor that in. However, in what order should events occur?

Assume the player is at (1, 1) and wishes to move right to (2, 1). That is one block, technically an atomic move with no wiggle room in between. Yet I need to transition the player from one block to the other as if it were a fluid movement. I figured that it's best to display the animation first, and when it's complete, i.e. the player is visually on the next grid, I change the player's position to reflect the change on the screen.

So far so good, but my code was having none of it. A function accepting (level) was absolutely ill-equipped to deal with adding animations. Where and how? I needed a change.

Therefore, I've changed my architecture from a single-state game to a state machine. I've the Game State and during each move I change the state to Playing, or something. It's still conceptual, but I see no reason why that shouldn't work. It's the exact same as the Game State, but input is disallowed and an animation is playing. Maybe Slicks can stack states...

Anyhow! This let me implement a menu at least:



The background is not made by me. In fact, I just... kinda took it. The wallpaper can be found here: http://www.wallpick.com/paint-black-wallpaper.html If it turns out that I probably shouldn't do this (I don't think I should), then I'll replace it with something free like Reiner's tiles.

A Victory and Defeat screen is also planned and should be quickly completed, but aren't yet.

5
7DRLs / Re: Arena (7DRL 2014)
« on: March 11, 2014, 01:03:49 PM »
Not too much to report this time. I gave the game a different skin (couresy of Reiner's skins: http://www.reinerstilesets.de/) and it looks a lot better now, albeit the walls are still piss-ugly.



I ran into a performance bottleneck recently. My grid is 100x100 large, or 10,000 tiles. I didn't mind it at the time, but rendering the entire map at every call is incredibly expensive! My framerate dropped below thirty and with the additional pressure of logging it stooped to a meager 15-17fps. Absolutely horrible- something has to be done!

I've since calculated the effective viewing area by working out a rectangle with my player in the center. Anything outside of this rectangle is not drawn. It works fine enough, even for edge cases such as the player being in a corner, but the code is horrendously long and ugly. I'll have to refactor it, but I'm unsure of what better methods exist for such calculations. Maybe somebody could provide assistance?

Code: [Select]
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)
    }

Otherwise I smoothed out the input handling. Every input now comes with its type, whether it was pressed or held down. This is useful, because I want a player to keep moving if they keep the key pressed, but not to keep eating walls. That's now possible, yay!

6
7DRLs / Re: Arena (7DRL 2014)
« on: March 11, 2014, 10:31:46 AM »
Good morning and a happy new day to everyone!

I said I would try to work off my list today and I'm happy to say that it's going well so far!

1) Input

Input is complete. It didn't work as nicely as I had hoped (does that ever happen? :P), but it works, so I have that going for me which is nice. The player has nine commands to choose from at the moment- sounds more than it is, see for yourself:

Code: [Select]
# Key bindings
moveUp: W
moveLeft: A
moveDown: S
moveRight: D

toggleWallLeft: Left
toggleWallRight: Right
toggleWallUp: Up
toggleWallDown: Down

shoot: Space

Shooting doesn't actually work yet, so just ignore it for the moment. Each of the keys is presented in a human-readable format- as a character from the keyboard. Slick doesn't use any of those, instead changing them to numbers internally. There also isn't a convenient function to transform a character to a keycode, so that had to be done by hand. Nevertheless, once you have that little nuisance out of the way, you have key-bound inputs!

Code: [Select]
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
    }
}

Which I map to commands in my Game class:

Code: [Select]
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)

This code says, "Each key is mapped to a function which accepts a Level and returns nothing (Unit). These functions are free-floating functions contained within a file:

Code: [Select]
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) })
}

And voila! We can now eat walls :)


7
7DRLs / Re: Arena (7DRL 2014)
« on: March 10, 2014, 08:56:01 PM »
Alright, last post behind me! No more programming for the day!

What did I accomplish:

1) Rendered a level
2) Introduced a player
3) Gave player movement
4) Level scrolling centered on the player
5) Almost scripted key bindings

For having only very few hours today- four or so- I think I accomplished quite a bit! Just wish I had a little more time in the day, but if you're sick and need to visit a doctor, well, programming be damned. Just glad everything is better and I'm all set to spend the majority of tomorrow programming!

With any luck tomorrow will consist of:

1) Completing input handling
2) Let the player do "something"- I think building and destroying walls could be fun.
3) Add an enemy
4) Let the player die

and if I'm really ambitious

5) Implement a death screen.

Who knows, I might just sit on my ass, eat chips, and watch speed runs :P Best of luck to everyone and I hope you have a great evening/day/morning wherever you're currently at!

8
7DRLs / Re: Arena (7DRL 2014)
« on: March 10, 2014, 08:48:11 PM »
For my last post I wanted to talk about inputs and handling them.

In the last few revisions of my code the player directly chose which inputs he would return as a direction. This is hard-coding the key to a certain behavior, even though the behavior was already abstracted. However, a player should not be able to pick and choose which inputs to ignore and which to honor, especially not which key should activate which behavior!

Therefore an InputController was born. The name reeks of OOP at its worst. Perhaps I should create no such class and have only one single function:  updateInput(): KeyMap, however, that's a story for another day.

Code: [Select]
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
    }
}

I've still hard-coded all the keys for now. I don't have the mental wherewithall to properly edit my settings to allow for key bindings, but that's a feature I will finish tomorrow. For now it's nice to know that inputs simply loops through a list of available keys and returns whether or not this key was pressed- not held down. Holding and pressing a key create two very distinct behaviors- movement is a lot smoother if I let a user keep a key pressed, but certains actions should not follow the same rules. It's something I'll have to refactor.

The settings should also contain the information about what action each key will perform. This is also currently hard-coded, but since the settings is a list of key-value pairs, it shouldn't be so difficult to describe.

Code: [Select]
[Action] = [Key]
MOVE_LEFT = A
MOVE_UP = W
SHOOT = SPACE
...

I read these key bindings and then create some convenient wrapper-functions such as isMovementKey(input) which will check the key against the action is was mapped to. I can then process that action. Commands are a Design Pattern excellently suited for this task and, time permitting, I will implement exactly that. For now, a simple if-else tree will suffice:

Code: [Select]
   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)

9
7DRLs / Re: Arena (7DRL 2014)
« on: March 10, 2014, 05:47:50 PM »
We can scroll now! I'd post a gif but my filming program (Mirillis Action) spectacularly crashed. I guess it's not quite ready for Windows 8. A gif of a scrolling block isn't that exciting anyway, so let's get right to the nitty gritty details :)

I didn't know how to scroll, and now, after reading a few posts:

http://gamedev.stackexchange.com/questions/44256/how-to-add-a-scrolling-camera-to-a-2d-java-game
http://slick.ninjacave.com/forum/viewtopic.php?t=1906
http://www.java-gaming.org/index.php/topic,24800.0

I think I know how to do it for *this* scenario at least. You want to be able to render your map in local coordinates, but transform the world. This is silly graphics speak for "Draw it like you usually do, but move the camera instead." Let's take an example:

We start with a 4x10 grid.

Code: [Select]
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

But wait! You can only see 3x3 of these at any time.

Code: [Select]
[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

Okay, and now we want to scroll it. Draw it like usual...

Code: [Select]
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

And now move the camera instead!

Code: [Select]
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

Draw as usual, move the world.

The moving the world part is accomplished by translating the current view by an x and y amount.

Code: [Select]
fun renderGameplay(graphics: Graphics, use: () -> Unit) {
graphics.translate(-cameraCenter.x.toFloat(), -cameraCenter.y.toFloat())

And you shouldn't ever forget to un-translate so you can draw the GUI without wondering where the hell it went.

Code: [Select]
fun renderGameplay(graphics: Graphics, use: () -> Unit) {
    graphics.translate(-cameraCenter.x.toFloat(), -cameraCenter.y.toFloat())

    use()

    graphics.translate(cameraCenter.x.toFloat(), cameraCenter.y.toFloat())
}

I use a function argument that will render the gameplay within the translated context and then automatically un-translate the view before I return. This way, I should hopefully never forget to reset anything :)

Code: [Select]
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())
    }
}

And to render:

Code: [Select]
override fun render(gameContainer: GameContainer?, graphics: Graphics?) {
    graphics!!.setBackground(Color.white)

    camera.renderGameplay(graphics) {
        levelSkin.render(level)
    }
}

10
7DRLs / Re: Arena (7DRL 2014)
« on: March 10, 2014, 03:20:54 PM »
Okay, so I implemented a "Player" with basic input. I'm not happy with the implementation by any stretch of the imagination, but it's a first result.

A player, like everything, can be configured from the settings, but only has HitPoints and Attack so far. I managed to get around the grid issue by treating the player as a FloorType, of which there are now three.

Code: [Select]
enum class FloorType {
    Floor
    Wall
    Player
}

This is cool, because my Level was already a catalog of x,y coordinates for everything in the level. A player is nothing but an entry in that catalog. Moving the Player along the grid isn't anymore complicated than shifting its position and making sure I erase the old one. That's a nice solution.

I once read a book called Game Development something something. It explained that you shouldn't handle input directly. Instead, the result of any input should be a Request- what the input should do. The main program interprets how to fulfill that request. This means that directional movement results in a Direction which is then interpreted by my main program as "Move the x,y coordinates of the player". Since it's an implementation detail, it shouldn't be directly visible to the Player.

Nevertheless, I'm not sure all of the logic should be in the Arena class or I run the risk of creating a God object. Anywho, it's still a task I'm working on and, quite frankly, I need a game loop or the speed of inputs will run out of control. It is quite fun to see my little player zip around the screen though :)

Player:

Code: [Select]
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
        }
    }
}

Input interpretation:

Code: [Select]
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
    }
}

11
Hey deepshock!

I found your thread :) The idea sounds interesting. I've seen a game that tried to blend a Fighter with rock-paper-scissors. Each fighter was allowed to input three commands (kick, punch, guard) and start the round. A kick would beat a punch, but lose to a guard, which then counters, or something like that. Perhaps such a turn-based system with pre-emptive commands would be suitable for a fighter roguelike?

Anyhow, I hope you post some pictures so we can see the progress, not just read it :) Keep at it and good luck!

12
7DRLs / Re: Arena (7DRL 2014)
« on: March 10, 2014, 01:48:15 PM »
@deepshock:  Thank you for your motivating comments! I'm glad you find some use in the various snippets of code I've posted here :) I'll be sure to return the favor once I dig out your thread!

---

The beast lives!



It's not much, but I enjoy it when a concept works. I've created a Skin class which loads all relevant tiles from my asset directory per the settings:

Code: [Select]
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)
            }
        }
    }
}

As you can see, rendering the level became a piece of cake with the help of just One More Abstraction. Martin Fowler never ceases to disappoint or blow my mind. I believe this is a good separation of responsibilities:  the level knows its layout, the skin knows how to render the layout.

I'll start adding the character with some movement now, or I'll implement a Grid. I'm pretty sure I'm going to need a grid real soon so I can use the mouse when selecting items, monsters, or the ground.

13
7DRLs / Re: Arena (7DRL 2014)
« on: March 10, 2014, 08:39:48 AM »
Good morning everyone! I hope everyone's feeling well.

I left off yesterday with my unsuccessful rendering of the Cave. I didn't address the issue yet because using a naked array in code is just brrrr. Can't have that yo. So we wrap that shizzle in a class and provide accessors and iterators to smooth out the handling.

Code: [Select]
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
        }
    }
}

This has several advantages.

1) No longer having to do array arithmetic at every other point in the code.
2) We can use the (x,y) coordinates which feels a lot more natural
3) It's easy to extend the class with utility functions such as iterators and iterables
4) We still have the base layout to work with and can "decorate" this with Skins

Skins are unimplemented yet, but they will provide the look and feel of my level. With this it should be trivial to swap one sprite pallette out for another. Even though this is a seven day roguelike and roguelikes aren't necessarily known for graphics, I believe this is a useful feature to have. A single entry in the settings.yml should be enough to quickly switch between different palettes. In any case, I'll probably only create a single palette because seven days is a damn short timeframe.

I've sadly encountered a bug that causes my level to StackOverflow if I use any of the Iterable extension functions (map, filter, count, find, and others). There's a corresponding issue open with the great Kotlin guys, and hopefully that will be fixed before I need it outside of testing!

Speaking of testing, what a life saver! I now have the (illusion of) guarantee that my accessors and iterators work 100% for levels of varying size. That's pretty cool.

14
7DRLs / Re: Arena (7DRL 2014)
« on: March 09, 2014, 06:41:41 PM »
Oh! One last thing. I encourage almost everyone who is able to to create a settings file! It makes testing and development so much easier :) Many things I would have to recompile the code for can now be easily managed via the settings.yml

Code: [Select]
# 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

If you're using Java, loading a yaml file is nothing more than importing SnakeYaml and creating a little loading function:

Code: [Select]
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)
}

15
7DRLs / Re: Arena (7DRL 2014)
« on: March 09, 2014, 06:38:48 PM »
Slicks is being mean. For some reason it won't draw my wall and floor tiles correctly. If anybody has any experience with Slicks, mind telling me what's wrong?

Code: [Select]
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())
    }
}

Anyway, that's it for the day! It's growing late and I'm pretty tired :)

What happened today:

- Project was set up
- Features and Milestones were set, repository created
- Map generation has reached a nice alpha
- Drawing on the screen almost works

I guess that's okay, especially for not having programmed a roguelike before. Hopefully I'll pick up some extra speed tomorrow. Have fun everyone!

Pages: [1] 2