ในการสร้างเกม Tic-Tac-Toe เราจะต้องตั้งค่า Rust บนเครื่องของเรา

ติดตั้งสนิมและสนิม

ขั้นแรก คุณจะต้อง "ติดตั้ง Rust" ฉันแนะนำให้ใช้ Rustup เพื่อติดตั้ง Rust ได้อย่างง่ายดายและอัปเดตอยู่เสมอ

รันคำสั่งนี้เพื่อติดตั้ง Rustup:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

สิ่งนี้จะติดตั้ง Rustup และ Rust เวอร์ชันเสถียรล่าสุด

สร้างโครงการ Rust ใหม่ด้วย Cargo

เมื่อติดตั้ง Rust แล้ว เราสามารถสร้างโปรเจ็กต์ Rust ใหม่โดยใช้ Cargo ซึ่งเป็นตัวจัดการแพ็คเกจของ Rust

รันคำสั่งนี้:

cargo new tic-tac-toe

สิ่งนี้จะสร้างโฟลเดอร์ชื่อ tic-tac-toe พร้อมไฟล์เริ่มต้นบางไฟล์ ไฟล์ที่สำคัญที่สุดสองไฟล์คือ:

  • src/main.rs - นี่คือไฟล์ต้นฉบับหลักที่โค้ดของเราจะไป
  • Cargo.toml - นี่คือไฟล์ Manifest ของ Cargo ที่ใช้จัดการการขึ้นต่อกัน ฯลฯ

เราทุกคนพร้อมแล้วและพร้อมที่จะเริ่มสร้างเกม Tic-Tac-Toe ใน Rust! ในส่วนถัดไป เราจะออกแบบโครงสร้างพื้นฐานของเกม

ออกแบบเกม

ในการออกแบบเกม Tic-Tac-Toe เราจำเป็นต้องกำหนดโครงสร้างและแจงนับบางส่วนเพื่อเป็นตัวแทนของกระดานและผู้เล่น

struct TicTacToe {
    board: Vec<Vec<char>>,
    next_player: char, 
}
  • โครงสร้าง TicTacToe จะมีเวกเตอร์ 2 มิติเพื่อเป็นตัวแทนของบอร์ด รวมถึงช่องสำหรับติดตามผู้เล่นคนต่อไป (อาจเป็น 'X' หรือ 'O')
enum Player {
    X,
    O 
}
  • Player enum จะแสดงถึงตาของผู้เล่นคนใด - 'X' หรือ 'O'
fn get_open_spots(board: &Vec<Vec<char>>) -> Vec<(usize, usize)> {
    // Logic to get a list of open spots on the board
}
  • ฟังก์ชัน get_open_spots() จะสแกนเวกเตอร์บอร์ดและส่งคืนรายการสิ่งอันดับของดัชนีแถวและคอลัมน์ที่เปิดอยู่ สิ่งนี้จะมีประโยชน์เมื่อให้ผู้เล่นย้ายเพื่อตรวจสอบอินพุต

นอกจากนี้เรายังต้องการให้ฟังก์ชันตรวจสอบการชนะ (3 รายการในแนวนอน แนวตั้ง หรือแนวทแยง) และตรวจสอบว่ามีการเสมอกันหรือไม่ (ไม่มีจุดเปิดบนกระดานอีกต่อไป) ฟังก์ชั่นเหล่านี้จะเป็นกุญแจสำคัญในการนำตรรกะของเกมไปใช้อย่างเต็มรูปแบบ

ด้วยโครงสร้างที่กำหนดเพื่อเป็นตัวแทนของกระดานเกม การแจงนับสำหรับผู้เล่น และฟังก์ชันเพื่อรับจุดเปิดและตรวจสอบผลลัพธ์ของเกม เราได้ออกแบบพื้นฐานของเกม Tic-Tac-Toe ใน Rust! ในส่วนถัดไป เราจะดูวิธีแสดงกระดานให้ผู้เล่นเห็น

แสดงกระดาน

เพื่อแสดงสถานะปัจจุบันของบอร์ด เราจะกำหนดฟังก์ชัน print_board():

fn print_board(board: &Vec<Vec<char>>) {
    for row in 0..3 {
        for col in 0..3 {
            print!("{} ", board[row][col]);
        }
        println!();
    }
}

ฟังก์ชั่นนี้จะวนซ้ำแต่ละแถวและคอลัมน์ของกระดานและพิมพ์อักขระที่ตำแหน่งนั้น (ทั้ง 'X', 'O' หรือ '_')

เราสามารถเรียกใช้ฟังก์ชันนี้หลังจากการเคลื่อนไหวแต่ละครั้งเพื่อพิมพ์สถานะบอร์ดที่อัปเดตสำหรับผู้เล่น:

// Make a move
make_move(board, player);

// Print updated board state
print_board(&board);

สิ่งนี้จะช่วยให้มองเห็นกระดานได้ชัดเจนสำหรับผู้เล่นหลังจากแต่ละเทิร์น บอร์ดอาจมีลักษณะดังนี้:

_ X O 
X _ _
O _ _

เมื่อเกมดำเนินไปและมีการเคลื่อนไหวมากขึ้น สถานะของบอร์ดจะอัปเดตต่อไปสำหรับผู้เล่นจนกว่าจะมีคนได้ 3 อันติดต่อกันหรือเต็ม 9 ช่องทั้งหมด (เสมอกัน)

การแสดงสถานะของกระดานด้วยภาพเป็นส่วนสำคัญของการนำเกม Tic-Tac-Toe ไปใช้ ต่อไป เราจะดูการรับข้อมูลจากผู้เล่นเพื่อวาง X หรือ O บนกระดาน

รับอินพุตของผู้เล่น

หากต้องการรับข้อมูลจากผู้เล่นในการเคลื่อนไหว เราจะกำหนดฟังก์ชัน get_move() มันจะแจ้งผู้เล่นที่ถึงเทิร์นสำหรับแถวและคอลัมน์ที่ต้องการวางเครื่องหมาย (X หรือ O)

fn get_move(board: &mut TicTacToe) -> (usize, usize) {
    println!("Player {}", board.next_player);
    let mut row = String::new();
    let mut col = String::new();

    io::stdin().read_line(&mut row)
        .expect("Failed to read line");
    io::stdin().read_line(&mut col)
        .expect("Failed to read line");

    let row: usize = row.trim().parse()
        .expect("Please enter a valid row number");
    let col: usize = col.trim().parse()
        .expect("Please enter a valid column number");

    (row, col)
}

ฟังก์ชั่นนี้จะพิมพ์พรอมต์สำหรับผู้เล่นปัจจุบัน (X หรือ O) จากนั้นจะอ่านอินพุตสองบรรทัดจากคอนโซลสำหรับแถวและคอลัมน์ อินพุตจะถูกแยกวิเคราะห์จากสตริงเพื่อใช้จำนวนเต็มและส่งกลับเป็นสิ่งทูเพิล

เราตรวจสอบอินพุตเพื่อให้แน่ใจว่าผู้ใช้ป้อนตัวเลขที่ถูกต้องสำหรับแถวและคอลัมน์ หากป้อนข้อมูลไม่ถูกต้อง ข้อความแสดงข้อผิดพลาดจะถูกพิมพ์และผู้ใช้จะได้รับแจ้งอีกครั้ง

ฟังก์ชัน get_move() ถูกใช้ภายใน Game Loop ของเราเพื่อรับการเคลื่อนไหวถัดไปจากผู้เล่นปัจจุบันในแต่ละเทิร์น ด้วยการจัดการการตรวจสอบอินพุตและการจัดการข้อผิดพลาด เรามอบประสบการณ์ผู้ใช้ที่ราบรื่นสำหรับเกม Tic-Tac-Toe ของเรา

ตรวจสอบการชนะหรือเสมอกัน

เพื่อตรวจสอบว่ามีคนชนะเกมหรือจบลงด้วยการเสมอกัน เราจำเป็นต้องตรวจสอบสถานะของกระดานหลังแต่ละกระบวนท่า เราจะกำหนดฟังก์ชัน check_win() เพื่อทำสิ่งนี้

fn check_win(board: &Vec<Vec<char>>) -> Option<char> {
    // Check for a win in the rows
    for row in 0..3 {
        if board[row][0] == board[row][1] && board[row][1] == board[row][2] {
            return Some(board[row][0]);
        }
    }
    // Check for a win in the columns
    for col in 0..3 {
        if board[0][col] == board[1][col] && board[1][col] == board[2][col] {
            return Some(board[0][col]);
        } 
    }  
    // Check for a win in the diagonals
    if board[0][0] == board[1][1] && board[1][1] == board[2][2] {
        return Some(board[0][0]);
    }
    if board[0][2] == board[1][1] && board[1][1] == board[2][0] {
        return Some(board[0][2]); 
    }
    // Check for a tie
    let mut empty_spots = 0;
    for row in 0..3 {
        for col in 0..3 {
            if board[row][col] == ' ' {
                empty_spots += 1;
            }
        }
    }
    if empty_spots == 0 {
        return Some(' ');
    }
    // No winner or tie yet
    None 
}

ฟังก์ชั่นนี้จะตรวจสอบเงื่อนไขการชนะที่เป็นไปได้ทั้งหมด — แถว คอลัมน์ และเส้นทแยงมุม นอกจากนี้ยังนับจำนวนจุดว่างที่เหลืออยู่บนกระดานเพื่อตรวจสอบการเสมอกัน หากตรงตามเงื่อนไขการชนะ ระบบจะส่งคืนผู้เล่นที่ชนะ (Some('X') หรือ Some('O')) หากเต็มทุกจุด จะส่งกลับ Some(' ') เพื่อระบุว่าเสมอกัน มิฉะนั้นจะส่งกลับ None เพื่อแสดงว่าเกมควรดำเนินต่อไป

เราใช้ฟังก์ชันนี้หลังจากการเคลื่อนไหวแต่ละครั้งเพื่อตรวจสอบว่าเกมจบลงแล้วหรือไม่ หาก check_win() ส่งกลับค่าใดค่าหนึ่ง เราสามารถแสดงข้อความแสดงความยินดีหรือผูกเน็คไทที่เหมาะสมแก่ผู้เล่นได้

เคลื่อนไหว

ในการดำเนินการ เราจะกำหนดฟังก์ชัน make_move ที่รับแถวและคอลัมน์จากอินพุตของผู้เล่น และวาง X หรือ O บนกระดาน แล้วสลับผู้เล่นคนถัดไปหลังจากนั้น

fn make_move(&mut self, row: usize, col: usize) {
    assert!(row < 3 && col < 3, "Invalid move");
    assert!(self.board[row][col] == '-', "Invalid move, space taken");

    if self.next_player == 'X' {
        self.board[row][col] = 'X';
        self.next_player = 'O';
    } else {
        self.board[row][col] = 'O';
        self.next_player = 'X';
    }
}

ก่อนอื่นเรายืนยันว่าการย้ายนั้นถูกต้องโดยตรวจสอบว่าแถวและคอลัมน์อยู่ในขอบเขตและพื้นที่ว่าง

จากนั้นเราวาง X หรือ O ไว้บนกระดาน ขึ้นอยู่กับตาของใคร เรายังเปลี่ยนฟิลด์ next_player เป็นผู้เล่นอื่นด้วย เพื่อที่พวกเขาจะได้ไปต่อ

ฟังก์ชันนี้เปลี่ยนโครงสร้าง TicTacToe โดยการอัพเดตบอร์ดและฟิลด์ next_player ตอนนี้เรามีตรรกะทั้งหมดในการรับข้อมูล เคลื่อนไหว ตรวจสอบชัยชนะ และเล่นเกมต่อ!

ส่วนถัดไปจะใช้ Game Loop หลักเพื่อเชื่อมโยงทั้งหมดเข้าด้วยกัน

ห่วงเกม

เพื่อใช้งาน Game Loop หลัก เราจะ:

  • กำหนดฟังก์ชัน game_loop()
  • มีวงวนที่ดำเนินต่อไปในขณะที่ไม่มีผู้ชนะหรือเสมอกัน
  • ภายในวง:
  • เรียกใช้ฟังก์ชัน get_player_move() เพื่อรับตัวเลือกแถวและคอลัมน์ของผู้เล่น
  • เรียกใช้ฟังก์ชัน make_move() เพื่อวาง X หรือ O บนกระดาน
  • เรียกใช้ฟังก์ชัน check_for_win() เพื่อตรวจสอบว่ามีผู้ชนะหรือเสมอกัน
  • หากส่งคืน Some(X) หรือ Some(O)) เราก็ได้ผู้ชนะ! หลุดออกจากวงแล้วพิมพ์ข้อความแสดงความยินดี
  • หากส่งคืน None ให้ดำเนินการวนซ้ำต่อไป
  • สลับ next_player จาก X เป็น O หรือกลับกันเพื่อเปลี่ยนรอบ

นี่คือตัวอย่างการใช้งาน:

fn game_loop() {
    loop {
        let (row, col) = get_player_move(); // Get player move
        make_move(row, col); // Make the move

        match check_for_win() { // Check if there is a winner or tie
            Some(player) => { // If there is a winner
                println!("Player {} wins!", player);
                break;
            }
            None => { // If no winner, continue the loop
                set_next_player!(); // Switch players
            }
        }
    }
}

วงนี้จะดำเนินต่อไปจนกว่าจะมีผู้ชนะ จากนั้นจะมีการพิมพ์ข้อความแสดงความยินดีและออกจากวง หากไม่มีช่องสี่เหลี่ยมว่างเหลืออยู่ การเรียก check_for_win() จะตัดสินว่าเสมอกัน และการวนซ้ำก็จะออกเช่นกัน

ข้อความแสดงความยินดี

เมื่อเรามีผู้ชนะหรือเสมอกัน เราต้องการแสดงข้อความที่เหมาะสมแก่ผู้เล่น เราสามารถเพิ่มตรรกะต่อไปนี้ให้กับ Game Loop ของเราได้:

let mut winner = None;

loop {
    // Get player input and make move

    winner = check_for_win(&board);

    if winner == Some('X') || winner == Some('O') {
        break;
    }

    if board.iter().all(|row| row.iter().all(|&cell| cell == 'X' || cell == 'O')) {
        println!("It's a tie!");
        break;
    }
} 

match winner {
    Some('X') => println!("Congratulations X! You win!"),
    Some('O') => println!("Congratulations O! You win!"),
    None => {}  // Do nothing, we already printed tie message 
}

เราตรวจสอบชัยชนะหลังจากการเคลื่อนไหวแต่ละครั้งโดยการเรียกใช้ฟังก์ชัน check_for_win() ของเรา หากเรามีผู้ชนะ เราจะออกจากวงและพิมพ์ข้อความแสดงความยินดีโดยจับคู่กับผู้เล่นที่ชนะ มิฉะนั้น เราจะตรวจสอบว่าบอร์ดเต็มหรือไม่โดยวนซ้ำแถวและคอลัมน์ทั้งหมด - หากเป็นเช่นนั้น เราจะพิมพ์ข้อความผูกและออกจากลูป

นี่เป็นข้อความปิดท้ายที่ดีที่จะแสดงต่อผู้เล่นของเราเมื่อเกมจบลง ไม่ว่าจะเพราะชนะหรือเสมอกัน! ผู้ใช้จะรู้สึกถึงความสำเร็จเมื่อเห็นข้อความแสดงความยินดี และพอใจที่พวกเขาสามารถปฏิบัติตามและสร้างโปรแกรม Rust เพื่อเล่น Tic-Tac-Toe!

บทสรุป

ในบทความนี้ เราได้สร้างเกม Tic-Tac-Toe ใน Rust ตั้งแต่เริ่มต้น! เราเริ่มต้นด้วยการจัดตั้งโครงการ Rust ใหม่กับ Cargo จากนั้นเราออกแบบเกมโดยกำหนดโครงสร้าง TicTacToe เพื่อแสดงสถานะของบอร์ดและเขียนฟังก์ชันเพื่อพิมพ์บอร์ด รับข้อมูลจากผู้เล่น ตรวจสอบชัยชนะ และทำการเคลื่อนไหว

struct TicTacToe {
    board: Vec<Vec<char>>,
    next_player: char 
}

เราสร้างเกมลูปเพื่อรับข้อมูลซ้ำแล้วซ้ำอีกและทำการเคลื่อนไหวจนกว่าจะชนะหรือเสมอกัน

loop {
    // Get input and make move
    check_win(); // Check for win or tie
    if let Some(winner) = check_win() {
        println!("{} is the winner!", winner);
        break;
    }
    if check_tie() {
        println!("It's a tie!");
        break;
    }
}

ในที่สุด เราได้เพิ่มตรรกะในการพิมพ์ข้อความแสดงความยินดีหากมีผู้ชนะ หรือข้อความการเสมอกันหากจบลงด้วยการเสมอกัน

ระบบประเภทที่เข้มงวด โมเดลการเป็นเจ้าของ และการมุ่งเน้นไปที่ความปลอดภัยทำให้เราสามารถสร้าง Tic-Tac-Toe ได้โดยไม่ต้องกังวลกับการเคลื่อนไหวที่ไม่ถูกต้อง การเข้าถึงนอกขอบเขต หรือปัญหานามแฝงที่ไม่แน่นอน ฉันหวังว่าสิ่งนี้จะช่วยให้คุณเห็นจุดแข็งของ Rust และเป็นแรงบันดาลใจให้คุณสร้างสิ่งที่สนุกสนานใน Rust!

โปรดปรบมือให้กับบทความนี้หากคุณชอบและพบว่าเนื้อหามีประโยชน์! ฉันขอขอบคุณข้อเสนอแนะหรือข้อเสนอแนะสำหรับหัวข้อในอนาคตเช่นกัน โปรดแจ้งให้เราทราบหากคุณมีคำถามอื่นๆ เกี่ยวกับการพัฒนาเกม Rust

ฉันหวังว่าบทความนี้จะเป็นประโยชน์กับคุณ! หากคุณพบว่ามีประโยชน์ โปรดสนับสนุนฉันด้วยการ 1) คลิกปรบมือ และ 2) แบ่งปันเรื่องราวไปยังเครือข่ายของคุณ โปรดแจ้งให้เราทราบหากคุณมีคำถามใดๆ เกี่ยวกับเนื้อหาที่ครอบคลุม

โปรดติดต่อฉันได้ที่ coderhack.com(at)xiv.in