防止两名球员在瑞士式比赛中被两次匹配

Prevent two players from being matched twice in Swiss Style Tournament

本文关键字:两次 两名      更新时间:2023-09-26

我目前正在尝试设计一个简单的瑞士风格锦标赛生成器,作为一种使用jquery的方式,并满足我自己的好奇心。然而,我遇到了一点障碍。

我已经建立了一个算法,在回合结束时根据获胜,平局和失败的数量对玩家进行排序,但我需要一些方法来防止玩家与相同的对手匹配两次。

当前玩家存储在偶数玩家与奇数玩家匹配的数组中(例如,playersarray[0] vs playersarray[1], playersarray[2] vs playersarray[3]等)。数组中的每个玩家都是一个对象,具有属性name, win, draw, loss, lostGame(用于确定谁输了,输了都是平局)和hasPlayed(存储已经通过名称匹配的玩家)。

这是我现在的排序算法:

//---SORTING ARRAYS----
//Danish Style (Players can play the same Player twice)
//Sort loss to bottom
playersArray.sort(function(a,b){
    return parseFloat(a.loss) - parseFloat(b.loss);    
});
//Sort Draw to top
playersArray.sort(function(a,b){
    return parseFloat(b.draw) - parseFloat(a.draw);    
});
//Sort with wins on top
playersArray.sort(function(a,b){
    return parseFloat(b.win) - parseFloat(a.win);    
});
//Swiss Style (make it so Players can't play the same opponet twice)
for(var j = 0; j < playersArray.length; j +=2){
    if($.inArray(playersArray[j].name, playersArray[j+1].haveplayed) != -1){
        var tempI = playersArray[j];
        playersArray[j] = playersArray[j-1];
        playersArray[j-1] = tempI;
        console.log("Matched!");
    }
}

我现在的解决方案是非常糟糕的,因为它只是交换了一个玩家,如果位置0和1玩家匹配,则不起作用。

对于如何解决这个问题,我将非常感激。

首先,我不会比较赢/平/输的数量,而是一个一般的分数:

赢+1平+0输-1

一般分数也有一个好处,让你更容易检查"接近分数"规则:

在随后的回合中,每个参赛者面对一个累积分数相同或几乎相同的对手。

接下来,我将稍微分解一下代码——但这是个人喜好。如果你有一个快速的小算法总是好的,但从长远来看,它可能会损害可读性并使事情复杂化。为什么不将过去的对局存储在Player实例中,并检查两个玩家是否已经有过对局(甚至存储结果)?

我写了一个小例子,将其分解成更清晰的组件(比赛和球员)。当然,这可以进一步优化和缩短,但这样写主要是为了让您对每一步都有一个清晰的概述。它基于wiki页面提供的规则。 https://jsfiddle.net/ovkktbg6/5/

// settings
var MAX_SCORE_DIFFERENCE = 2;
// match
function Match(player1, player2, result) {
    this.player1 = player1
    this.player2 = player2
    // player1 [won/draw/lost] against player2
    this.result = result
    // give each player the match results
    this.player1.addMatch(this)
    this.player2.addMatch(this)
    console.log(player1.name, result == Match.RESULT_WIN ? 'won' : ( result == Match.RESULT_LOSS ? 'lost' : 'draw' ), 'against', player2.name, '-', player1.score, ':', player2.score)
}
// possible results
Match.RESULT_WIN  = 1
Match.RESULT_DRAW = 0
Match.RESULT_LOSS = -1
Match.randomResult = function() {
    return Math.floor( Math.random() * (Match.RESULT_WIN - Match.RESULT_LOSS + 1) ) + Match.RESULT_LOSS;
}
// player
function Player(name) {
    this.name    = name // just to have any identification
    this.score   = 0
    this.wins    = 0
    this.losses  = 0
    this.draws   = 0
    this.matches = []
}
Player.prototype.addMatch = function( match ) {
    this.matches.push(match)
    if( match.result == Match.RESULT_DRAW ) {
        this.draws++;
    } else {
        // check if the first player is this one
        if( match.player1 == this ) {
            // this player1 has WON against player2
            if(match.result == Match.RESULT_WIN) {
                this.wins++;
                this.score++;
            } else {
                this.wins--;
                this.score--;
            }
        // this player is player2
        } else {
            // player1 has LOST against this player2
            if(match.result == Match.RESULT_LOSS) {
                this.wins++;
                this.score++;
            } else {
                this.wins--;
                this.score--;
            }
        }
    }
}
Player.prototype.hasPlayedAgainst = function( player ) {
    // a player canot play against him/herself
    if( this == player ) return false;
    // check the other matches
    for( var i = 0; i < this.matches.length; i++ ) {
        var match = this.matches[i]
        if( match.player1 == player || match.player2 == player) return true; 
    }
    return false;
}
// example
var playerList = []
playerList.push( new Player('Alex') )
playerList.push( new Player('Bob') )
playerList.push( new Player('Charles') )
playerList.push( new Player('David') )
playerList.push( new Player('Erik') )
playerList.push( new Player('Franz') )
playerList.push( new Player('Georg') )
playerList.push( new Player('Hans') )
playerList.push( new Player('Ian') )
playerList.push( new Player('Jacob') )
playerList.push( new Player('Karl') )
playerList.push( new Player('Lars') )
playerList.push( new Player('Marco') )
// if the matchups should be random each time, the playerList can be:
// randomly ordered once, here - the tournament will have random matchups
// or randomly ordered inside the while() loop. Every tournament round will have random matchups then
// play through the tournament
// pick every player in the playerList and match them against every other player
var round         = 0
var matchPossible = true
while( matchPossible ) {
    // this flag is set to true if there was a match
    // if no match was played, that means that the tournament is over, since every player already competed against each other or there are no similar enough scores to play
    matchPossible = false;
    // this loop goes through the whole player list the first time, picking player1
    for( var i = 0; i < playerList.length; i++ ) {
        var player1 = playerList[i];
        // exclude players who already played this round
        if( player1.matches.length > round ) {
            continue;
        }
        // this loop goes through the whole player list once more, picking player2
        // the two loops match every player against every player, skipping their match with the conditions below
        for( var ii = 0; ii < playerList.length; ii++ ) {
            var player2 = playerList[ii];
            // do not play against him/herself
            if( player1 == player2 ) {
                continue;
            }
            // exclude players who already played this round
            if( player2.matches.length > round ) {
                continue;
            }
            // the two already faced each other
            if( player1.hasPlayedAgainst( player2 ) ) {
                continue;
            }
            // their scores are too far apart
            if( Math.abs( player1.score - player2.score ) > MAX_SCORE_DIFFERENCE ) {
                continue;
            }
            // there was a match!
            matchPossible = true
            // if we get here, the players should face each other
            new Match( player1, player2, Match.randomResult() );
            // break out, let the next players have a match
            break;
        }
    }
    // start the next round
    round++;
}
// the tournament ended - let's rank them.
playerList.sort( function( player1, player2 ) {
    return player2.score - player1.score;
} ) 
// print the results
console.log('-----')
for( var i = 0; i < playerList.length; i++ ) {
    var player = playerList[i];
    console.log('Rank', i + 1, ':', player.name, 'with', player.score, 'points');
}

编辑:这样分解的另一个好处是你不必太担心聪明的排序算法之类的东西——每个组件都根据给定的规则做它的工作,最后你评估结果。

它没有经过密集的测试,所以很有可能它仍然有一些隐藏的bug,但根据我所做的几次测试,它应该可以正常工作。

Edit:根据要求,我将尝试进一步解释循环。两个for()循环除了首先选择player1,然后将player1与playerList中的每个玩家(每个玩家都称为player2)进行匹配之外,基本上没有做任何其他事情。当当前player1与每个player2匹配时,将选择下一个player1。

我们设Alex为player1:

Alex (player1) is matched against Alex (player2) [no match - same player]
Alex (player1) is matched against Bob (player2) [no match - already had a match]
Alex (player1) is matched against Charles (player2) - they have a match!
Now the nested `for( var ii )` loop is broken with `break;`, and Alex (player1) is done for the moment. We pick the next player in the playerList:
Bob (player1) is matched against Alex (player2) [no match - already had a match]
Bob (player1) is matched against Bob (player2) [no match - same player]
Bob (player1) is matched against Charles (player2) [no match - charles already had a match this round]
Bob (player1) is matched against David (player2) - they have a match!
Now the nested `for( var ii)` loop breaks a second time, and we continue with Charles
Charles (player1) is matched against Alex (player2) [no match - already had a match]
etc...

同时,while( ... )循环在每一轮比赛中进行:每一轮,所有玩家都要互相检查,直到我们达到一个点,即任何玩家都不允许与任何其他玩家对抗。比赛至此结束,我们可以根据比分对playerList进行排序。

Edit (2):只是为了更好地可视化我对continuebreak关键字的解释,因为很难在注释中这样做:(请记住,这只是用于表示目的的伪代码)

for( loop A ) {
    for( loop B ) {
         continue; // just skip to the end of loop B, and go through loop B again!
         ... this stuff is skipped by continue ...
         END OF LOOP <- continue gets us here! loop A is unaffected
    }
}
休息

for( loop A ) {
    for( loop B ) {
         break; // completly jump out of loop B!
         ... this stuff is skipped by break ...
    }
    <- break gets us here! loop B is broken out of, we just continue with the code in loop A
    ... stuff that is NOT skipped by break! ...
}