Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Very simple RogueLike in F#, making it more "functional"

I have some existing C# code for a very, very simple RogueLike engine. It is deliberately naive in that I was trying to do the minimum amount as simply as possible. All it does is move an @ symbol around a hardcoded map using the arrow keys and System.Console:

//define the map
var map = new List<string>{
  "                                        ",
  "                                        ",
  "                                        ",
  "                                        ",
  "    ###############################     ",
  "    #                             #     ",
  "    #         ######              #     ",
  "    #         #    #              #     ",
  "    #### #### #    #              #     ",
  "       # #  # #    #              #     ",
  "       # #  # #    #              #     ",
  "    #### #### ######              #     ",
  "    #              =              #     ",
  "    #              =              #     ",
  "    ###############################     ",
  "                                        ",
  "                                        ",
  "                                        ",
  "                                        ",
  "                                        "
};

//set initial player position on the map
var playerX = 8;
var playerY = 6;

//clear the console
Console.Clear();

//send each row of the map to the Console
map.ForEach( Console.WriteLine );

//create an empty ConsoleKeyInfo for storing the last key pressed
var keyInfo = new ConsoleKeyInfo( );

//keep processing key presses until the player wants to quit
while ( keyInfo.Key != ConsoleKey.Q ) {
  //store the player's current location
  var oldX = playerX;
  var oldY = playerY;

  //change the player's location if they pressed an arrow key
  switch ( keyInfo.Key ) {
    case ConsoleKey.UpArrow:
      playerY--;
      break;
    case ConsoleKey.DownArrow:
      playerY++;
      break;
    case ConsoleKey.LeftArrow:
      playerX--;
      break;
    case ConsoleKey.RightArrow:
      playerX++;
      break;
  }

  //check if the square that the player is trying to move to is empty
  if( map[ playerY ][ playerX ] == ' ' ) {
    //ok it was empty, clear the square they were standing on before
    Console.SetCursorPosition( oldX, oldY );
    Console.Write( ' ' );
    //now draw them at the new square
    Console.SetCursorPosition( playerX, playerY );
    Console.Write( '@' );
  } else {
    //they can't move there, change their location back to the old location
    playerX = oldX;
    playerY = oldY;
  }

  //wait for them to press a key and store it in keyInfo
  keyInfo = Console.ReadKey( true );
}

I was playing around with doing it in F#, initially I was trying to write it using functional concepts, but turned out I was a bit over my head, so I did pretty much a straight port - it's not really an F# program (though it compiles and runs) it's a procedural program written in F# syntax:

open System

//define the map
let map = [ "                                        ";
            "                                        ";
            "                                        ";
            "                                        ";
            "    ###############################     ";
            "    #                             #     ";
            "    #         ######              #     ";
            "    #         #    #              #     ";
            "    #### #### #    #              #     ";
            "       # #  # #    #              #     ";
            "       # #  # #    #              #     ";
            "    #### #### ######              #     ";
            "    #              =              #     ";
            "    #              =              #     ";
            "    ###############################     ";
            "                                        ";
            "                                        ";
            "                                        ";
            "                                        ";
            "                                        " ]

//set initial player position on the map
let mutable playerX = 8
let mutable playerY = 6

//clear the console
Console.Clear()

//send each row of the map to the Console
map |> Seq.iter (printfn "%s")

//create an empty ConsoleKeyInfo for storing the last key pressed
let mutable keyInfo = ConsoleKeyInfo()

//keep processing key presses until the player wants to quit
while not ( keyInfo.Key = ConsoleKey.Q ) do
    //store the player's current location
    let mutable oldX = playerX
    let mutable oldY = playerY

    //change the player's location if they pressed an arrow key
    if keyInfo.Key = ConsoleKey.UpArrow then
        playerY <- playerY - 1
    else if keyInfo.Key = ConsoleKey.DownArrow then
        playerY <- playerY + 1
    else if keyInfo.Key = ConsoleKey.LeftArrow then
        playerX <- playerX - 1
    else if keyInfo.Key = ConsoleKey.RightArrow then
        playerX <- playerX + 1

    //check if the square that the player is trying to move to is empty
    if map.Item( playerY ).Chars( playerX ) = ' ' then
        //ok it was empty, clear the square they were standing on
        Console.SetCursorPosition( oldX, oldY )
        Console.Write( ' ' )
        //now draw them at the new square 
        Console.SetCursorPosition( playerX, playerY )
        Console.Write( '@' )
    else
        //they can't move there, change their location back to the old location
        playerX <- oldX
        playerY <- oldY

    //wait for them to press a key and store it in keyInfo
    keyInfo <- Console.ReadKey( true )

So my question is, what do I need to learn in order to rewrite this more functionally, can you give me some hints, a vague overview, that kind of thing.

I'd prefer a shove in the right direction rather than just seeing some code, but if that's the easiest way for you to explain it to me then fine, but in that case can you please also explain the "why" rather the "how" of it?

like image 491
nrkn Avatar asked Dec 21 '10 03:12

nrkn


People also ask

How hard is it to code a roguelike?

The answer is easy: Anything. The slightly longer answer is that it doesn't really matter a whole lot. If you already have experience with some language then that's great, go ahead and use it. Language is a means to an end, and people have used just about every language to create a roguelike before.

What is an example of a roguelike game?

Some of the better-known variants include Hack, NetHack, Ancient Domains of Mystery, Moria, Angband, Tales of Maj'Eyal, and Dungeon Crawl Stone Soup. The Japanese series of Mystery Dungeon games by Chunsoft, inspired by Rogue, also fall within the concept of roguelike games.

What is the original roguelike?

The title of "roguelike" comes from the game that pioneered this type of gameplay: Rogue, released for various early computers starting in 1980. Rogue is a turn-based dungeon crawler game where you have to fight your way through levels of a dungeon, picking up items and defeating enemies along the way.


1 Answers

Game programming in general will test your ability to manage complexity. I find that functional programming encourages you to break problems your solving into smaller pieces.

The first thing you want to do is turn your script into a bunch of functions by separating all the different concerns. I know it sounds silly but the very act of doing this will make the code more functional (pun intended.) Your main concern is going to be state management. I used a record to manage the position state and a tuple to manage the running state. As your code gets more advanced you will need objects to manage state cleanly.

Try adding more to this game and keep breaking the functions apart as they grow. Eventually you will need objects to manage all the functions.

On a game programming note don't change state to something else and then change it back if it fails some test. You want minimal state change. So for instance below I calculate the newPosition and then only change the playerPosition if this future position passes.

open System

// use a third party vector class for 2D and 3D positions
// or write your own for pratice
type Pos = {x: int; y: int} 
    with
    static member (+) (a, b) =
        {x = a.x + b.x; y = a.y + b.y}

let drawBoard map =
    //clear the console
    Console.Clear()
    //send each row of the map to the Console
    map |> List.iter (printfn "%s")

let movePlayer (keyInfo : ConsoleKeyInfo) =
    match keyInfo.Key with
    | ConsoleKey.UpArrow -> {x = 0; y = -1}
    | ConsoleKey.DownArrow -> {x = 0; y = 1}
    | ConsoleKey.LeftArrow -> {x = -1; y = 0}
    | ConsoleKey.RightArrow  -> {x = 1; y = 0}
    | _ -> {x = 0; y = 0}

let validPosition (map:string list) position =
    map.Item(position.y).Chars(position.x) = ' '

//clear the square player was standing on
let clearPlayer position =
    Console.SetCursorPosition(position.x, position.y)
    Console.Write( ' ' )

//draw the square player is standing on
let drawPlayer position =
    Console.SetCursorPosition(position.x, position.y)
    Console.Write( '@' )

let takeTurn map playerPosition =
    let keyInfo = Console.ReadKey true
    // check to see if player wants to keep playing
    let keepPlaying = keyInfo.Key <> ConsoleKey.Q
    // get player movement from user input
    let movement = movePlayer keyInfo
    // calculate the players new position
    let newPosition = playerPosition + movement
    // check for valid move
    let validMove = newPosition |> validPosition map
    // update drawing if move was valid
    if validMove then
        clearPlayer playerPosition
        drawPlayer newPosition
    // return state
    if validMove then
        keepPlaying, newPosition
    else
        keepPlaying, playerPosition

// main game loop
let rec gameRun map playerPosition =
    let keepPlaying, newPosition = playerPosition |> takeTurn map 
    if keepPlaying then
        gameRun map newPosition

// setup game
let startGame map playerPosition =
    drawBoard map
    drawPlayer playerPosition
    gameRun map playerPosition


//define the map
let map = [ "                                        ";
            "                                        ";
            "                                        ";
            "                                        ";
            "    ###############################     ";
            "    #                             #     ";
            "    #         ######              #     ";
            "    #         #    #              #     ";
            "    #### #### #    #              #     ";
            "       # #  # #    #              #     ";
            "       # #  # #    #              #     ";
            "    #### #### ######              #     ";
            "    #              =              #     ";
            "    #              =              #     ";
            "    ###############################     ";
            "                                        ";
            "                                        ";
            "                                        ";
            "                                        ";
            "                                        " ]

//initial player position on the map
let playerPosition = {x = 8; y = 6}

startGame map playerPosition
like image 59
gradbot Avatar answered Nov 08 '22 09:11

gradbot