Очень простой RogueLike в F #, делающий его более «функциональным» - PullRequest
7 голосов
/ 21 декабря 2010

У меня есть некоторый существующий код C # для очень, очень простого движка RogueLike.Это намеренно наивно, потому что я пытался сделать минимальную сумму как можно проще.Все, что он делает, это перемещает символ @ по жестко закодированной карте с помощью клавиш со стрелками и 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 );
}

Я играл с этим в F #, первоначально я пытался написать его, используя функциональные концепции, но оказалось, что я немного над головой, поэтому я сделал довольно прямой порт - это не действительно программа F # (хотя она компилируется и запускается), это процедурная программа, написанная в синтаксисе F #:

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 )

Итак, мой вопрос: что мне нужно выучить, чтобы переписать это более функционально, не могли бы вы дать мне несколько советов, смутный обзор, такого рода вещи.

Я бы предпочел толчок в правильном направлении, а не просто увидеть какой-то код, но если это самый простой способ объяснить это мне, тогда хорошо, но в таком случае вы можете также объяснить "почему "скорее" как "из этого?

Ответы [ 4 ]

9 голосов
/ 21 декабря 2010

Программирование игр в целом проверит вашу способность управлять сложностью. Я считаю, что функциональное программирование побуждает вас разбивать решаемые вами задачи на более мелкие части.

Первое, что вы хотите сделать, это превратить ваш скрипт в набор функций, разделив все различные задачи. Я знаю, это звучит глупо, но сам акт делает этот код более функциональным (каламбур). Ваша главная задача - управление состоянием. Я использовал запись для управления состоянием позиции и кортеж для управления состоянием работы. По мере того, как ваш код становится более продвинутым, вам понадобятся объекты для чистого управления состоянием.

Попробуйте добавить больше в эту игру и продолжайте разбивать функции на части по мере их роста. В конце концов вам понадобятся объекты для управления всеми функциями.

В заметке о программировании игры не меняйте состояние на что-либо другое, а затем меняйте его обратно, если оно не проходит какой-либо тест. Вы хотите минимальное изменение состояния. Например, ниже я вычисляю newPosition и затем изменяю playerPosition только если эта будущая позиция пройдет.

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
4 голосов
/ 21 декабря 2010

Хорошая маленькая игра :-). В функциональном программировании вы хотели бы избегать использования изменяемого состояния (как указывали другие), а также вы хотели бы написать ядро ​​вашей игры как функцию, которая не имеет побочных эффектов (например, чтение из консоли и запись).

Ключевой частью игры является функция, которая контролирует положение. Вы можете изменить свой код, чтобы иметь функцию с сигнатурой типа:

val getNextPosition : (int * int) -> ConsoleKey -> option<int * int>

Функция возвращает None, если игра должна выйти. В противном случае возвращается Some(posX, posY), где posX и posY - ваши новые местоположения для символа @. Внеся изменения, вы получите хорошее функциональное ядро, а функцию getNextPosition также легко проверить (поскольку она всегда возвращает один и тот же результат для одних и тех же входных данных).

Чтобы использовать функцию, лучше всего написать цикл с использованием рекурсии. Структура основной функции будет выглядеть следующим образом:

let rec playing pos =
  match getNextPosition pos (Console.ReadKey()) with
  | None -> () // Quit the game
  | Some(newPos) ->
     // This function redraws the screen (this is a side-effect,
     // but it is localized to a single function)
     redrawScreen pos newPos
     playing newPos
2 голосов
/ 21 декабря 2010

Будучи игрой и используя Консоль, здесь присутствуют состояние и побочные эффекты.Но главное, что вы хотите сделать, - это удалить эти изменчивые файлы.Использование рекурсивного цикла вместо цикла while поможет вам сделать это, с тех пор вы можете передавать свое состояние в качестве аргументов каждому рекурсивному вызову.Кроме этого, главное, что я могу увидеть, чтобы воспользоваться преимуществами функций F # здесь, - это использование сопоставления с образцом вместо операторов if / then и переключателей, хотя это было бы в основном эстетическим улучшением.

1 голос
/ 21 декабря 2010

Я постараюсь не быть слишком конкретным - если я в конечном итоге захожу слишком далеко в другом направлении, и это слишком расплывчато, дайте мне знать, и я постараюсь немного его улучшить.

КогдаПри создании функциональной программы, которая имеет какое-то состояние, базовый механизм, который вы хотите реализовать, выглядит примерно так:

(currentState, input) => newState

Затем вы можете написать небольшую оболочку для обработки выборки ввода и вывода рисунка.

...