F#中的非常简单的RogueLike,使其更“功能化” [英] Very simple RogueLike in F#, making it more "functional"

查看:90
本文介绍了F#中的非常简单的RogueLike,使其更“功能化”的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一些现有的C#代码,用于一个非常非常简单的RogueLike引擎。这是故意幼稚的,因为我试图尽可能简单地做到最低限度的金额。它所做的就是使用箭头键和System.Console在硬编码地图上移动@符号:

  //定义地图
var map = new List< string> {




#### ###########################,
##,
####### #,
####,
#### #######,
######,
#######,
#### #### #######,
#=#,
#=#,
###############################,





};

//在地图上设置初始玩家位置
var playerX = 8;
var playerY = 6;

//清除控制台
Console.Clear();

//将地图的每一行发送到控制台
map.ForEach(Console.WriteLine);

//创建一个空的ConsoleKeyInfo来存储按下的最后一个键
var keyInfo = new ConsoleKeyInfo();

//继续处理按键直到玩家想要退出
while(keyInfo.Key!= ConsoleKey.Q){
//存储玩家的当前位置
var oldX = playerX;
var oldY = playerY;

//改变玩家的位置(如果他们按下了一个箭头键)
switch(keyInfo.Key){
case ConsoleKey.UpArrow:
playerY--;
休息;
案例ConsoleKey.DownArrow:
playerY ++;
休息;
案例ConsoleKey.LeftArrow:
playerX--;
休息;
案例ConsoleKey.RightArrow:
playerX ++;
休息;
}

//检查玩家试图移动的方格是否为空
if(map [playerY] [playerX] ==''){
//确定它是空的,在
之前清除它们所在的方块Console.SetCursorPosition(oldX,oldY);
Console.Write('');
//现在将它们绘制在新的方形
Console.SetCursorPosition(playerX,playerY);
Console.Write('@');
} else {
//他们不能在那里移动,将他们的位置改回原位置
playerX = oldX;
playerY = oldY;
}

//等待他们按下一个键并将其存储在keyInfo
keyInfo = Console.ReadKey(true);
}

我在F#中玩弄了一下,最初我正在写它使用的是功能概念,但事实证明我有点过头了,所以我做了一个非常直接的端口 - 它不是一个真正的F#程序(虽然它编译和运行),它是一个编写的过程程序在F#语法中:

 打开系统

//定义地图
let map = [;
;
;
;
###############################;
##;
########;
####;
#### #######;
######;
######;
#### #### #######;
#=#;
#=#;
###############################;
;
;
;
;
]

//在地图上设置初始玩家位置
让可变玩家X = 8
让可变玩家Y = 6

//清除控制台
Console.Clear()

//将地图的每一行发送到控制台
map |> Seq.iter(printfn%s)

//创建一个空的ConsoleKeyInfo来存储按下的最后一个键
let mutable keyInfo = ConsoleKeyInfo()

/ /继续处理按键,直到玩家想要退出
而不是(keyInfo.Key = ConsoleKey.Q)do
//存储玩家的当前位置
让mutable oldX = playerX
let mutable oldY = playerY

//改变玩家的位置,如果他们按下箭头键
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

//检查玩家试图移动到的方格是否为空
如果map.Item(playerY).Chars(playerX)=''则
//确定它是空的,清除他们站在
上的方块Console.SetCursorPosition(oldX,oldY)
Console.Write('')
//现在在新的方块
Console.SetCursorPosition(playerX,playerY)
Console.Write('@')
else
//他们不能在那里移动,将他们的位置改回旧位置
playerX< - oldX
playerY< - oldY

//等待他们按一个键并将其存储在keyInfo
keyInfo< - 控制台中。 ReadKey(true)

所以我的问题是,我需要学习什么才能重写更多在功能上,你能给我一些提示,一个模糊的概述,那种事情。



我更喜欢向正确的方向推一下,而不是只看到一些代码,但如果这是最简单的方法让你向我解释,那么很好,但在这种情况下,请你也解释一下为什么而不是怎么样的问题?

解决方案

您管理复杂性的能力。我发现函数式编程鼓励您将问题解决为小块。



您要做的第一件事是将脚本分成所有函数不同的担忧。我知道这听起来很愚蠢,但这样做的行为将使代码更加实用(双关意图)。您的主要关注点将是国家管理。我用一个记录来管理位置状态和一个元组来管理运行状态。随着代码变得更加先进,您需要对象来干净地管理状态。



尝试在游戏中添加更多内容,并随着游戏规模的不断扩大而将功能分开。最终你需要一些对象来管理所有的功能。



在游戏编程说明中,不要将状态改为别的东西,如果测试失败,请将其改回。你想要最小的状态改变。因此,举例来说,下面我计算 newPosition ,然后只有在这个未来的位置通过时才更改 playerPosition

 打开系统

//为二维和三维位置使用第三方向量类
//或编写自己的练习
类型Pos = {x:int; y:int}
with
static member(+)(a,b)=
{x = a.x + b.x; y = ay + by}

让drawBoard map =
//清除控制台
Console.Clear()
//将地图的每一行发送给控制台
map |> List.iter(printfn%s)

让movePlayer(keyInfo:ConsoleKeyInfo)=
匹配keyInfo.Key和
| 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)=''

//清除广场玩家站在
上让clearPlayer position =
Console.SetCursorPosition(position.x,position.y)
Console.Write('')

//绘制方形玩家站在
上让drawPlayer position =
Console.SetCursorPosition(position.x,position.y)
Console.Write('@')

let takeTurn map playerPosition =
let keyInfo = Console.ReadKey true
//检查玩家是否想继续玩
let keepPlaying = keyInfo.Key< > ConsoleKey.Q
//从玩家输入获取玩家动作
让movement = movePlayer keyInfo
//计算玩家新位置
让newPosition = playerPosition +移动
/ /检查有效移动
let validMove = newPosition |> validPosition map
//如果移动有效,则更新绘图
if validMove then
clearPlayer playerPosition
drawPlayer newPosition
//返回状态
if validMove then
keepPlaying,newPosition
else
keepPlaying,playerPosition

//主游戏循环
让rec gameRun map playerPosition =
let keepPlaying,newPosition = playerPosition |> takeTurn map
if keepPlaying then
gameRun map newPosition

//安装游戏
let startGame map playerPosition =
drawBoard map
drawPlayer playerPosition
gameRun map playerPosition


//定义地图
let map = [;
;
;
;
###############################;
##;
########;
####;
#### #######;
######;
######;
#### #### #######;
#=#;
#=#;
###############################;
;
;
;
;
]

//地图上的初始玩家位置
let playerPosition = {x = 8; y = 6}

startGame map playerPosition


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?

解决方案

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

这篇关于F#中的非常简单的RogueLike,使其更“功能化”的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆