rust语言与go语言_开始使用Rust语言进行编码

本文通过构建井字游戏介绍Rust编程语言,涵盖了项目设置、游戏逻辑、验证、机器人玩家和游戏结束条件等内容,适合Rust初学者跟随实践。
摘要由CSDN通过智能技术生成

rust语言与go语言

正如我在本系列的第一部分中提到的,我真的很喜欢Rust。 这种静态编译的语言是内存安全的,并且与操作系统无关,因此可以在任何计算机上运行。 Rust为您提供了系统语言的速度和低级的好处,而无需像C#和Java这样的讨厌的垃圾收集方法。

没有比真正开始使用语言更好的学习语言的方法。 本文通过向您展示如何使用该语言构建简单的井字游戏,来帮助您使用Rust。 遵循以建立自己的有趣的游戏。

先决条件

首先阅读本系列的第一部分,Rust入门指南。 我将向您展示如何安装和运行Rust,描述其核心功能,并向您介绍入门所需的概念。 在本文中,我不会描述该语言的所有方面,因此您需要掌握该语言的基础知识。

开始项目

首先,您需要设置您的项目。 您可以使用Cargo在终端上创建新的可执行二进制程序:

$ cd ~/Documents
$ cargo new tic_tac_toe –bin

在树形程序中,新的tic_tac_toe目录如下所示:

$ cd tic_tac_toe
$ tree .
.
??? Cargo.toml
??? src
    ??? main.rs

main.rs文件应包含以下几行:

fn main() {
    println!("Hello, world!");
}

清单1所示,运行程序与创建程序一样容易。

清单1.运行“ Hello,World!”
$ cargo build
    Compiling …
     Finished …
$ cargo run
     Finished …
      Running …
Hello, world!

现在,您还需要一个用于游戏模块的文件。 通过执行以下命令行来创建此文件:

$ touch ./src/game.rs

通过项目和目录设置,您可以深入了解游戏概述。

用类型和结构计划游戏

经典的井字游戏由两个主要部分组成:一个棋盘和一个针对每个玩家的回合。 棋盘实质上是一个空的3x3阵列,而转牌则指示哪个玩家必须移动。 要转换此功能,必须编辑在上一节中创建的game.rs文件(请参见清单2)。

清单2.为板和玩家回合修改的Game.rs
type Board = Vec<Vec<String>>;

enum Turn {
    Player,
    Bot,
}

pub struct Game {
    board: Board,
    current_turn: Turn,
}

您可能已经在这里注意到了奇怪的语法,但是请不要担心:我将在本文中进行描述。

董事会

要翻译游戏板,请使用type关键字为名称Board别名,以使其与Vec<Vec<String>>类型同义。 现在, Board是一个二维字符串矢量的简单类型。 我在这里使用char是因为数组中唯一的值将是xo或一个指示未平仓头寸的数字。

转弯

回合只是表明哪个玩家必须选择位置,因此enum结构可以完美地发挥作用。 在每个回合中,只需匹配Turn变体即可进行适当的方法调用。

游戏

最后,您必须创建一个Game对象,该对象包含棋盘和当前正在玩的回合。 可是等等! Game结构的方法在哪里? 不用担心:那就是下一个。

实施游戏

井字游戏由哪些方法组成? 好吧,有转弯。 在每一回合中,显示棋盘,玩家移动,再次显示棋盘,并检查获胜条件。 如果游戏获胜,游戏会宣布哪个玩家获胜,并要求他或她再次玩。 如果没有人赢得比赛,则游戏将切换当前玩家并进行下一回合。 显然,每个动作内部都有更好的问题,具体取决于玩家,但是您可以从这里潜入。

首先,创建一个嵌套在impl块中的构造,如清单3所示。

清单3.游戏构造
impl Game {
    pub fn new() -> Game {
        let first_row = vec![
            String::from("1"), String::from("2"), 
            String::from("3")];

        let second_row = vec![
            String::from("4"), String::from("5"), 
            String::from("6")];

        let third_row = vec![
            String::from("7"), String::from("8"), 
            String::from("9")];

        Game {
            board: vec![first_row, second_row, third_row],
            current_turn: Turn::Player,
        }
    }
}

静态方法new创建并返回Game结构。 这是Rust中对象构造函数的标准名称。

您必须将board成员变量与String对象的2d向量绑定。 请注意,我没有用空白填充每个位置,而是用一个数字来指示每个动作的可用位置。 接下来,将current_turn成员变量绑定到Turn::Player的值。 这条线意味着每个游戏都让玩家先行。

您如何玩游戏?

第一种方法用作程序的映射。 您添加内这种方法impl Grid块(连同方法在本节的其余部分)。 清单4显示了该方法。

清单4.游戏程序图
pub fn play_game(&mut self) {
    let mut finished = false;

    while !finished {
        self.play_turn();

        if self.game_is_won() {
            self.print_board();

            match self.current_turn {
                Turn::Player => println!("You won!"),
                Turn::Bot => println!("You lost!"),
            };

            finished = Self::player_is_finished();

            self.reset();
        }

        self.current_turn = self.get_next_turn();
    }
}

很容易看到游戏流程。 使用无限循环,您可以从一圈转向下一圈,交替使用current_turn 。 因此,您可以对self使用可变借项,因为游戏的内部状态每回合都会改变。

enum已经获得回报,因为如果赢得了这场比赛,则会嵌入有关谁赢得比赛的信息。 然后,您让玩家知道他或她赢了或输了。 此外,您还可以将板重置为原始状态,这对于用户再次玩游戏很有用。

请注意,这是除new之外的唯一pub方法。 这意味着play_gamenew是另一个库在使用Game对象时可以访问的唯一方法。 所有其他静态或静态方法都是私有的。

扭转潮流

play_game方法中使用的第一个辅助方法是play_turn 。 清单5显示了这个漂亮的小功能。

清单5. play_turn函数
fn play_turn(&mut self) {
    self.print_board();

    let (token, valid_move) = match self.current_turn {
        Turn::Player => (
            String::from("X"), self.get_player_move()),
        Turn::Bot => (
            String::from("O"), self.get_bot_move()),
    };

    let (row, col) = Self::to_board_location(valid_move);

    self.board[row][col] = token;
}

这是一个棘手的问题。 首先,您要打印电路板,以便用户知道可用的位置(即使是在机器人的时候也很有用)。 接下来,根据current_turn的变体,使用元组valid_movematch分配变量tokenvalid_move

token是玩家或机器人的String XO valid_move是1到9的整数,他在板上的位置没有被占用。 然后,使用to_board_location静态方法将此变量转换为电路板的相应行和列。 ( Self ,以一个大写字母“S”,返回类型的self -in这种情况下, Game 。)

让我们看看那个板子

现在您已经设置了play_turn ,您需要一种打印方法。 清单6显示了该方法。

清单6.打印游戏板
fn print_board(&self) {
    let separator = "+---+---+---+";

    println!("\n{}", separator);

    for row in &self.board {
        println!("| {} |\n{}", row.join(" | "), separator);
    }

    print!("\n");
}

在这种方法中,使用for循环在板上打印ASCII表示的行。 临时变量row是对电路板上每个向量的引用。 使用join方法,您可以将row转换为String并使用附加的分隔符String打印该新值。

现在可以使用打印功能,您终于可以继续为播放器和机器人获取有效的移动了。

玩家,该你了

到目前为止,该程序是一系列硬编码的返回,没有播放器的输入。 清单7对此进行了更改。

清单7.设置转弯
fn get_player_move(&self) -> u32 {
    loop {
        let mut player_input = String::new();

        println!(
            "\nPlease enter your move (an integer between \
            1 and 9): ");

        match io::stdin().read_line(&mut player_input) {
            Err(_) => println!(
                "Error reading input, try again!"),
            Ok(_) => match self.validate(&player_input) {
                Err(err) => println!("{}", err),
                Ok(num) => return num,
            },
        }
    }
}

该方法的核心归结为:除非玩家为游戏提供有效的动作,否则它会无限循环。

用户提示后的第一个匹配表达式尝试将用户的输入读入Stringplayer_input ,并检查这样做是否发生错误。 io模块提供了此功能。 您必须将此模块导入game.rs文件顶部 。 其stdin().read_line方法( stdin()返回当前标准输入的句柄对象)。 这是我导入的io模块:

use std::io;

同样重要的是要注意,当read_line方法使给定的String突变时,它还会返回一个称为Resultenum 。 我在介绍性文章中没有谈论Result ,因此我在后面进行讨论。

结果枚举

Result就是所谓的代数类型。 这个enum有两个变体: OkErr 。 每个变体都可以保存数据,例如Stringi32

read_line情况下,返回的Resultio模块的特殊版本,这意味着Err是特殊的io::Error变体。 相反, Ok与原始Result变体相同,并且在这种情况下,保留一个整数,该整数代表读取的字节数。 Result是一个有用的enum ,有助于确保您在编译时而不是运行时处理所有可能的错误。

在Rust中普遍存在的另一个同级enumOption 。 代替OkErr ,它的变体是None (不保存任何数据)和Some (可以保存)。 OptionC++中的nullptr或Python中的None有用的方式中很有用。

OptionResult什么区别,什么时候应该使用它们? 这是我的最佳答案。 首先,如果您期望函数什么都不会返回,则使用Option 。 将Result用于您希望一直成功但可能失败的函数,这意味着必须捕获错误。 得到它了? 大。 返回get_player_move方法。

回到游戏

我停止阅读播放器的输入。 如果在读取用户输入时发生错误,程序将通知用户并要求他或她再次输入。 如果没有错误发生,则程序到达第二个match表达式。 注意下划线( _ )的使用:它们告诉Rust您没有在ResultOkErr变体中绑定数据,而在第二个match表达式中进行了绑定。

match表达式检查player_input变量是否有效。 如果不是,则代码返回错误(游戏会提醒玩家注意),并要求玩家输入有效的信息。 如果player_input有效,则返回使用validate方法转换为整数的输入。

验证您的代码

编写了游戏的核心后,最好编写一个validate函数。 清单8显示了代码。

清单8. validate函数
fn validate(&self, input: &str) -> Result<u32, String> {
    match input.trim().parse::<u32>() {
        Err(_) => Err(
            String::from(
                "Please input a valid unsigned integer!")),
        Ok(number) => {
            if self.is_valid_move(number) {
                Ok(number)
            } else {
                Err(
                    String::from(
                        "Please input a number, between \
                        1 and 9, not already chosen!"))
            }
        }
    }
}

逐行运行此输出,这是方法的要点。

首先,程序返回一个Result enum 。 我没有介绍类型模板,但是基本上,您是在说ResultOk变体必须包含u32整数,而Err变体必须包含String 。 为什么Result在这里返回? 好吧,仅当给定输入为:该方法才有望通过并引发错误:

  • 不是整数;
  • 由于占用原因,不是有效的地点; 要么
  • 无效的位置,因为整数不是1–9。

接下来,程序尝试使用inputparse方法将input转换为u32turbofish, ::<type>是某些函数的一个特殊方面,它告诉他们要返回什么类型。 在这种情况下,它同时告诉parse尝试将input转换为u32并将ResultOk变量设置为容纳u32 。 如果无法转换input ,则代码将返回错误,指示input不是无符号整数。 但是,如果转换成功,代码将通过另一个帮助器函数is_valid_move传递input

为什么还有另一个辅助功能用于验证? 在较早的可能错误列表中,数字1是特定于用户的。 机器人将始终给出一个整数。 因此,您仅使用validate来验证玩家的React。 is_valid_move检查其他两个可能的错误。

清单9显示了验证代码的最后一部分。

清单9.更多验证
fn is_valid_move(&self, unchecked_move: u32) -> bool {
    match unchecked_move {
        1...9 => {
            let (row, col) = Self::to_board_location(
                unchecked_move);

            match self.board[row][col].as_str() {
                "X" | "O" => false,
                 _ => true,
            }
        }
        _ => false,
    }
}

很简单。 如果给定的unchecked_move不在1到9(含)之间,则这不是有效的移动。 否则,代码将被强制检查是否已经进行了移动。 像之前在play_turn ,您可以将unchecked_move转换为板上的相应行和列。 然后,您可以检查该位置是否在板上。 如果位置是XO ,则移动无效。

上机器人

在继续编写方法以使机器人行动之前,创建清单10所示的to_board_location静态方法。

清单10. to_board_location方法
fn to_board_location(game_move: u32) -> (usize, usize) {
    let row = (game_move - 1) / 3;
    let col = (game_move - 1) % 3;

    (row as usize, col as usize)
}

这种方法有点作弊的,因为你知道,当to_board_location就是所谓的validateplay_turn ,参数game_move为1和9(含)之间的整数。 您将此方法设置为静态,因为数学与Game对象没有联系。 井字游戏板始终为3x3。

聊天机器人

您的代码可以从玩家那里获得成功,但可以考虑使用机器人。 首先,漫游器的举动应该是随机数,这意味着您需要导入第三方板条箱rand 。 其次,使用is_valid_move方法继续生成此随机移动,直到到达有效位置is_valid_move 。 然后,游戏必须通知玩家机器人采取了什么行动,并退还该行动。

您可以将rand板条箱导入并安装在名为Cargo.toml的文件中,并将rand作为依赖项。 清单11显示了该文件。

清单11. Cargo.toml
[package]
name = "tic_tac_toe"
version = "0.1.0"
authors = ["Dylan Hicks <dirtgrub.dylanhicks@gmail.com>"]

[dependencies]
rand = "0.4"

main.js文件告诉Cargo您要使用此依赖项。 我将此命令放在文件的顶部:

extern crate rand;

然后,将此命令放在io import上方game.rs文件的顶部:

use rand;

使用rand crate生成一个随机数,您需要一种方法来从机器人中获得成功。 清单12显示了该方法。

清单12. bot_move方法
fn get_bot_move(&self) -> u32 {
    let mut bot_move: u32 = rand::random::<u32>() % 9 + 1;

    while !self.is_valid_move(bot_move) {
        bot_move = rand::random::<u32>() % 9 + 1;
    }

    println!("Bot played moved at: {}", bot_move);

    bot_move
}

那很轻松,对吗?

该方法结束了play_turn方法的依赖关系。 现在,您需要制定一种方法来检查游戏是否获胜。

我们是冠军

现在,您将快速而轻松地使用布尔代数( 清单13 )。

清单13.布尔布尔代数
fn game_is_won(&self) -> bool {
    let mut all_same_row = false;
    let mut all_same_col = false;

    for index in 0..3 {
        all_same_row |= 
            self.board[index][0] == self.board[index][1]
            && self.board[index][1] == self.board[index][2];
        all_same_col |= 
            self.board[0][index] == self.board[1][index]
            && self.board[1][index] == self.board[2][index];
    }

    let all_same_diag_1 =
        self.board[0][0] == self.board[1][1] 
        && self.board[1][1] == self.board[2][2];
    let all_same_diag_2 =
        self.board[0][2] == self.board[1][1] 
        && self.board[1][1] == self.board[2][0];

        (all_same_row || all_same_col || all_same_diag_1 || 
         all_same_diag_2)
}

for循环中,您同时检查行和列以查看是否满足了Tic-Tac-Toe的获胜条件(即,连续三个X或Os)。 您可以使用|=执行此操作,就像+= ,但是它使用or运算符代替加法运算符。 然后,您检查两个对角线是否都相同。 最后,通过使用布尔布尔代数返回是否满足任何获胜条件。 另外三种方法,您已完成。

您想再玩一次吗?

如果你回头看看play_game的方法清单4 ,您会看到该代码不断循环,直到finishedtrue 。 仅当方法player_is_finishedtrue时,才会发生这种情况。 此方法应基于玩家的响应:是或否( 清单14 )。

清单14. player_is_finished方法
fn player_is_finished() -> bool {
    let mut player_input = String::new();

    println!("Are you finished playing (y/n)?:");

    match io::stdin().read_line(&mut player_input) {
        Ok(_) => {
            let temp = player_input.to_lowercase();

            temp.trim() == "y" || temp.trim() == "yes"
        }
            Err(_) => false,
    }
}

当我最初编写此方法时,我决定最好只处理玩家输入的“是”情况,这意味着所有其他输入都返回false 。 同样,这是一种静态方法,因为它没有使用任何self携带的数据。

硬重置可修复所有问题

reset play_game使用的最后一种方法,如清单15所示。

清单15.重置方法
fn reset(&mut self) {
    self.current_turn = Turn::Player;
    self.board = vec![
        vec![
            String::from("1"), String::from("2"),  
            String::from("3")],
        vec![
            String::from("4"), String::from("5"), 
            String::from("6")],
        vec![
            String::from("7"), String::from("8"), 
            String::from("9")],
    ];
}

此方法所做的全部工作就是将游戏的成员变量设置回其默认值。

完成游戏所需的最后一个方法是get_next_turn ,如清单16所示。

清单16. get_next_turn方法
fn get_next_turn(&self) -> Turn {
    match self.current_turn {
        Turn::Player => Turn::Bot,
        Turn::Bot => Turn::Player,
    }
}

此方法仅检查打开了哪些self ,然后返回相反的self

运行并编译游戏

在game.rs模块完成之后,main.rs现在可以编译和玩游戏了( 清单17 )。

清单17.编译游戏
extern crate rand;

mod game;

use game::Game;

fn main() {
    println!("Welcome to Tic-Tac-Toe!");

    let mut game = Game::new();

    game.play_game();
}

而已。 您只是使用mod声明了该模块中存在游戏模块,并通过useGame对象引入了作用域。 然后,您使用Game::new()创建了一个game对象,并告诉该对象玩游戏。 现在,使用Cargo运行它( 清单18 )。

清单18.运行游戏
$ cargo run
   Compiling tic_tac_toe v0.1.0 …
    Finished dev [unoptimized + debuginfo] …
     Running …
Welcome to Tic-Tac-Toe!

+---+---+---+
| 1 | 2 | 3 |
+---+---+---+
| 4 | 5 | 6 |
+---+---+---+
| 7 | 8 | 9 |
+---+---+---+


Please enter your move (an integer between 1 and 9):
…

最后的想法

正如您在本教程中所学到的那样,Rust是一种通用语言,它易于使用Java, C#或Python,但具有CC++的速度和功能。 该代码不仅可以快速编译,而且所有内存和错误问题都在编译时而不是在运行时处理,从而减少了代码中可能出现的人为错误。

下一步


翻译自: https://www.ibm.com/developerworks/opensource/library/os-using-rust/index.html

rust语言与go语言

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值