【问题标题】:Recommended improved match-finding algorithm for Bejeweled game?为 Bejeweled 游戏推荐改进的匹配查找算法?
【发布时间】:2013-03-20 18:10:13
【问题描述】:

我正在尝试确定一种合理的方法来为每行和每列查找 3、4 或 5 个匹配项。玩家在游戏板中寻找相同“宝石”的区域(行或列),在交换两个相邻的棋子(每回合交换一个)后,重复 3-5 个连续点。

这是一个匹配移动的示例场景:

  • 玩家移动前的棋盘(粗体是必要的交换):

    A C B B C

    D D B A D

    D A A C C

    A D B B A

    D C D A A

  • 玩家移动后的棋盘(粗体为结果匹配):

    A C B B C

    D D B A D

    D A A C C

    D A B B A

    D C D A A

在此示例中,第一行之后的第一列中有 4 个匹配的“D”。我试图弄清楚如何在 1.) 游戏开始时创建棋盘并且棋盘随机化多次以消除即时匹配,以及 2.) 在玩家移动之后找到这样的匹配。如果工作正常,程序将能够在程序本身或玩家进行正确交换后检测到匹配。

我尝试的每个算法都导致循环超出范围和/或在交换后不正确地找到所有结果匹配项。我的意思是,程序有时会尝试在数组之外搜索,因为我没有成功地告诉程序如何根据当前位置的位置“调整”其数组搜索。即使它不会导致运行时错误,它仍然会显示不正确的结果。例如,玩家会看到棋盘上显示了至少一个完整匹配,这不好。

以下是我尝试过的两个过程的解释:

  • 以下空格。从当前位置,检查ahead在同一行中的四个空格(如果该行中剩余的空格少于四个,则检查更少)。首先检查五个匹配,包括当前位置;如果没有,检查四个(减去一个点);如果没有,检查三个(减去一个点);如果没有,则找不到匹配项。对下面的列重复相同的检查。

  • 前面的空格。从当前位置,检查后退在同一行中的四个空格(如果第一个位置之间的空格少于四个,则更少在行和当前位置)。首先检查五个匹配,包括当前位置;如果没有,检查四个(减去一个点);如果没有,检查三个(减去一个点);如果没有,则找不到匹配项。对 above 列重复相同的检查。

这是需要这种工作算法的主要功能。此处使用我的 Gem 类(当前已损坏)可能对请求并不重要,因此除非有帮助,否则我不会添加它。

bool Board::findMatches(bool scoringMove) // false if board is being setup before game
{
    bool matchesFound = false;

    // loops through entire board, where "size" is the width, not the number of spots
    for (int i = 0; i < size.getSize()*size.getSize(); i++)
    {
        // loops for each type of Gem, six total (_not identical to given example_)
        for (int k = 0; k < gems.getNumGems(); k++)
        {
            Gem traverseGems(k); // next Gem (in sequence)
            char nextGem = traverseGems.getGem(); // next Gem set to a char

            // ROWS check
            // default match search for 3-match
            if ((i < (size.getSize()*size.getSize())-4)
            && (board[i]->getGem() == nextGem)
            && (board[i+1]->getGem() == nextGem)
            && (board[i+2]->getGem() == nextGem))
            {
                // if the player is making a move
                if (!scoringMove)
                    return true;

                matchesFound = true;

                // just adds points to score; irrelevant to algorithm
                scoreMatches(3, 'R', i, 3);

                // no 4-match, but a 3-match
                if (board[i+3]->getGem() != nextGem)
                    scoreMatches(3, 'R', i, 3);
                else
                    scoreMatches(4, 'R', i, 4);

                // 5-match found
                if (board[i+3]->getGem() == nextGem && board[i+4]->getGem() == nextGem)
                    scoreMatches(5, 'R', i, 5);
            }

            // COLUMNS check (comments for rows check apply here as well)

            if ((i <= (size.getSize()-1))
            && (board[i]->getGem() == nextGem)
            && (board[i+size.getSize()]->getGem() == nextGem)
            && (board[i+(size.getSize()*2)]->getGem() == nextGem))
            {
                if (!scoringMove)
                    return true;

                matchesFound = true;

                scoreMatches(3, 'C', i, 3);

                if (board[i+(size*3)]->getGem() != nextGem)
                    scoreMatches(3, 'C', i, 3);
                else
                    scoreMatches(4, 'C', i, 4);
                if (board[i+(size*3)]->getGem() == nextGem && board[i+(size*4)]->getGem() == nextGem)
                    scoreMatches(5, 'C', i, 5);
            }
        }
    }

    return matchesFound;
}

Board.h

#ifndef BOARD_H
#define BOARD_H

#include "Size.h"
#include "Score.h"
#include "Move.h"
#include "Gem.h"

#include <iostream>
#include <iomanip>
#include <ctime>

class Board
{
private:
    Size size;
    Score score;
    Gem **board;
    bool moreSwaps;

    void swapGems(Move);
    void swapGems(int, int);
    void setNewRandGem(int);
    void findMoreSwaps();
    void scoreMatches(int, char, int, int);
    bool findMatches(bool);

public:
    Board();
    Board(Size, Score&);
    ~Board();
    void displayBoard() const;
    bool isMatch(Move);
    bool moreMovesFound() const;
};

#endif

电路板构造器

Board::Board(Size size, Score &score)
{
    srand((unsigned int)time(NULL)); // I can always move this to main()

    this->size = size;
    this->score = score;

    board = new Gem *[size.getSize()*size.getSize()];

    for (int i = 0; i < size.getSize()*size.getSize(); i++)
        board[i] = new Gem;

    //This is the "pre-game" block.
    //As long as the program finds a new match after performing its
    //own swaps, it'll randomize the entire board and start over again.
    //This is incredibly unefficient, but I will try to fix it later.
    do
    {
        for (int i = 0; i < size.getSize()*size.getSize(); i++)
            setNewRandGem(i);

    } while (findMatches(false));
}

【问题讨论】:

    标签: c++ arrays algorithm search


    【解决方案1】:

    重新阅读更新后的问题后,我认为您的目标是测试对于给定的 5x5 板,是否存在两个相邻符号的任何可能交换,从而产生具有 3 个或更多相同的板行或列中的符号。

    如果之前的尝试产生了越界错误,这意味着实现中存在错误,而不是算法中的错误。因此,使用不同的算法无法解决该问题,您仍然需要实施适当的数组边界检查。没有办法解决这个问题,但从好的方面来说,这并不是特别难。在访问数组之前,只需检查每个索引是否小于零或大于数组维度大小。如果是,请追溯您的程序用于获取该值的步骤,并找出必须存在的错误。

    当然,如果程序除了越界错误之外还产生错误的结果,那么你的算法可能也是错误的。

    话虽如此,我仍然不确定我是否理解你描述的算法,但对于这个问题来说它们似乎太复杂了。除非您需要每秒评估数千块板,否则简单的蛮力算法就足够了。只需尝试所有可能的交换,并为每个交换检查板是否在一行或一列中包含 3 个或更多相同的符号。

    下面是伪代码描述:

    function is_there_a_valid_move(board)
    // returns true if there is a valid bejewelled move
    
       // test all horizontal swaps
       for (x = 0; x++; x< board-width - 1):
          for (y = 0; y++; y < board-height):
             make a copy of the board: board2
             swap board2[x,y] and board2[x+1,y]
             check_matches(board2, 3)
             if match found: return true
    
       // test all vertical swaps
       for (x = 0; x++; x< board-width):
          for (y = 0; y++; y < board-height - 1):
             make a copy of the board: board2
             swap board2[x,y] and board2[x,y+1]
             check_matches(board2, 3)
             if match found: return true
    
       return false
    
    function check_matches(board, num_matches)
    // returns true if there are num_matches or more of the same symbol in a row or column
    
       // check rows
       for (y = 0; y++; y < board-height):
          consecutive_symbols = 0
          for (x = 0; x++; x< board-width - 1):
             if board[x,y] == board[x+1,y]: consecutive_symbols++
             else: consecutive_symbols = 0
             if consecutive_symbols >=num_matches: return true
    
       // check columns
       for (x = 0; x++; x< board-width):
          consecutive_symbols = 0
          for (y = 0; y++; y < board-height - 1):
             if board[x,y] == board[x,y+1]: consecutive_symbols++
             else: consecutive_symbols = 0
             if consecutive_symbols >=num_matches: return true     
    
       return false
    

    这当然不是最快的方法,但对于 5x5 板来说,其他一切都过大了。

    【讨论】:

    • 我之前可能应该澄清“越界”部分涉及防止每个水平/垂直检查超出该行或列。我可能需要根据您的代码调整部分代码,因为它比我的更有意义。我一开始就使用了“连续符号”的想法,但出于某种原因我把它拿出来了。总的来说,当只寻找 3 场比赛时,这要容易得多。这只是在比赛开始前完成的。在轮到球员的时候,如果问题太大,我可能只需要省略 4-5 比赛检查。
    • 但是 4 匹配也是 3 匹配。因此,如果您对 3/4/5-matches 感兴趣,只需检查 3-matches;如果您需要 4/5 匹配,请检查 4 匹配。如果您想知道是否存在 3 匹配但没有 4 匹配,或者相反,请重复检查两者。
    • 是的,我能做到。每个连续的宝石仍然可以添加到计数器中,并且最终的计数器值将添加到分数中。这是我从未真正考虑过的方法。例如,程序可以检查第四个元素是否有相同的 gem。如果不一样,就默认打分。如果相同,请检查五个。如果不一样,给四分。如果是的话,给五分。现在,我应该用它来回答我自己的问题吗?我不确定我是否会得到更好的答案,而且我以前没有做过自我回答。不过我还是会接受你的。
    • 我看不出问题所在:您可以检查 5 个连续符号的匹配并对其进行评分;如果没有检查 4 个或更多;如果没有,请检查 3 个或更多;这将始终得分最高的比赛。无需合并这些检查。关于自我回答:如果您找到解决方案或其中的一部分,回答您自己的问题当然是一种好习惯,这没有错。
    • 好的。我仍然不确定的一件事是防止支票进入其他行或列。这可能已经是您代码的一部分,但我尚未对其进行测试。我会继续考虑的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-06
    • 2019-04-19
    相关资源
    最近更新 更多