- Contacts
- Description
- World settings
- Settings
- Editor
- Teams
- strategies.js
- sport.js
- controls.js
- Discussion
Email: al_d@online.ua
Skype: dying_escape
This is random pointless page with league simulation. Games are based on Iterated prisoner's dilemma. One team uses strategy which may be updated via Editor tab.
Project is made in JavaScript (jQuery). Scripts:
- js/userfunc.js — user function for the team controlled by user.
- js/strategies.js — strategies for all other teams
- js/sport.js — basic league things like structure, scheduling, games and so on
- js/controls.js — basic control things
Main functions in file sport.js are next: initWorld, createTeamList, runSeason, runGame.
Idea is next
- There are strategies that can be used by teams in games. Each team uses one strategy (at least for a some time).
- Then there is League which is array of leagues [League 1, League 2, ..., League leagueLevelQ]. Each league has leagueTeamQ teams. exchangeTeamQ teams from each league will be promoted to upper league (League[lower index] if such exists) in the end of each season, and same number of teams will relegate accordingly. Check functions: makeLeagueStructure, makeLeagueSchedule, playLeagueGames, makeLeagueStandings.
- Each league (as League[i]) has league.teams (list of teams which play there in the given season), league.games (list of games in the given season), league.standings (calculated in the end of season). For example, check League[0] object in browser console.
- Each game stores teams (host is first), round (gameday), score, used tactics. Check function runGame and objects like League[0].games[0]
- Simulation is started with function initWorld and continued with function runSeason (called by the buttons Create new world and Run next season).
Note Objects in other objects are stored via pointers. Thus, for example, League[0].games[0].team[0] and League[0].standings[0].team do not store separate copies of items from teams array.
Project can be stored and modified locally (attention to the template elements in HTML-code).
Variable | Value | |
---|---|---|
Number of leagues | leagueLevelQ | |
Number of teams in league | leagueTeamQ | |
Number of teams in promotion/relegation zone | exchangeTeamQ |
League points
Variable | Value | |
---|---|---|
Win | points.w | |
Draw | points.d |
Points per decision pairs
Defection (D) | Cooperation (C) | |||
---|---|---|---|---|
Defection (D) | ipdPoints[0][0] | ipdPoints[0][1] | ||
Cooperation (C) | ipdPoints[1][0] | ipdPoints[1][1] |
Other settings
Team sorting parameters for league standings
points — points, w — wins, gd — goal difference, gf — goals for.
Write the function userfunc which returns 0 (defect) or 1 (cooperate). This function will be used by one of the teams as strategy in game (userfunc in righthand column in standings). this.memory stores the decisions of opponent. Each new decision of an opponent is always added to the beginning of this.memory array. See strategy.js for examples of strategies and usage of this.memory.
(Put it in work and in local storage.)
Team | Strategy | Level |
---|
// al_d@ukr.net // ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— var cooperate = 1 var defect = 0 // Array of strategies // StrategyName: Function which must set tactics of a given team in its next game // "this" in strategy functions refers to a team which calls it var strategies = { userfunc: userfunc, random: function() { // Random decision to cooperate or defect return Math.floor(Math.random() * 2) }, sucker: function() { // Always cooperate return cooperate }, evil: function() { // Always defect return defect }, tit4tat: function() { // Start from cooperate. Then copy the last decision of the opponent var m = typeof this.memory[0] === 'undefined' ? cooperate : this.memory[0] return m // ? cooperate : defect }, tit4twotats: function() { // Start from cooperate. Then cooperate if the last two decisions of the opponent were not defect var m1 = typeof this.memory[0] === 'undefined' ? cooperate : this.memory[0] var m2 = typeof this.memory[1] === 'undefined' ? cooperate : this.memory[1] return (m1 + m2) ? cooperate : defect }, }
// Author: Oleksa Vyshnivsky a.k.a ODE // al_d@ukr.net // ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // GLOBAL VARIABLES AND SETTINGS var seasonCalculationOn = false // Shows if the calculation of season is currently running var season = 0 // Current season var teams = [] // Holder of teams var League = [] // Holder of all Association data (except teams) var leagueLevelQ = 3 // Number of levels in Association var leagueTeamQ = 10 // Number of teams in league var exchangeTeamQ = 2 // Number of teams in one promotion or relagation zone // Points var points = { w: 3, ow: 2, d: 1, ol: 1, l: 0 } // Parameters for sorting the teams in league var leagueSortingParameters = [ 'points', 'gd', 'gf', 'w', ] // Points per decision [[DD, DC, CD, CC]] var ipdPoints = [[1, 3], [0, 2]] // Number of rounds in game var roundsMin = 50 var roundsMax = 99 // ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // GENERAL LEAGUE ACTIONS function createTeamList() { // Used for setting var strategiesKeys = Object.keys(strategies) // Total number of teams var teamQ = leagueLevelQ * leagueTeamQ // Array of teams teams = [] // Basic team data for (var t = 0; t < teamQ; t++) { // Random strategy for the given team var strategyKey = strategiesKeys[getRandomInt(1, strategiesKeys.length - 1)] teams.push({ name: 'Team ' + (t + 1), // Generic team names strategyName: strategyKey, // Just a key for easier reference to the name of used strategy decision: strategies[strategyKey], // Strategy for setting a tactic before each game memory: [], titles: 0, // Just the number of titles (1st places in League 1 only at this moment) level: 0 }) } teams[teamQ - 1].strategyName = strategiesKeys[0] teams[teamQ - 1].decision = strategies[strategiesKeys[0]] // Downloading a bit more interesting team names $.ajax({ async: false, method: 'GET', url: $('#birdnames').attr('href'), data: {n: teamQ} }).done(function(response) { // Response is in json format if requested via jquery ajax. Otherwise it's in plain text format (which is not used here) try { var names = JSON.parse(response) for (var t = 0; t < teamQ; t++) { teams[t].name = names[t] } } catch (e) {} }) } function createLeagueStructure() { League = [] for (var level = 0; level < leagueLevelQ; level++) { // Create league level var league = { level: level + 1, teams: [], games: [], standings: [] } // Select teams for this league for (var t = 0; t < leagueTeamQ; t++) { league.teams.push(teams[level * leagueTeamQ + t]) } League.push(league) } } function makeLeagueSchedule(league) { // Round up a number of teams in league to a closest even number var pseudoTeamsQ = leagueTeamQ + leagueTeamQ % 2 // Number of rounds (game days) in one round-robin go var roundQ = Math.ceil(leagueTeamQ / 2) * 2 - 1 // List of numbers which correspond to teams in league // Means: Flat list of teams which will be separated into team pairs in the second part of this function var teamRoundList = [] // First round — the simpliest list of teams: 0, 1, 2, ..., LastTeamN // This means next matches: Team 0 — Team 1; 2 — 3; ...; (LastTeamN - 1) — LastTeamN // Note: LastTeamN = pseudoTeamsQ - 1 var round = 1 teamRoundList[round] = [] for (var i = 0; i < pseudoTeamsQ; i++) teamRoundList[round].push(i) // Next rounds for (var round = 2; round < roundQ + 1; round++) { teamRoundList[round] = [] // First team is fixed teamRoundList[round].push(0) // Next teams are moved like clockwise in the game list // Example: If the games of previous round are these: // 0 — 1; 2 — 3; 4 — 5; 6 — 7 // than the games of given round will be these: // 0 — 2; 4 — 1; 6 — 3; 7 — 5 // where team 0 is fixed team, team 2 goes to position 1, team LastTeamN goes to position (LastTeamN - 1), // team Even goes to position (Even – 2), team Odd goes to position (Odd + 2) for (var i = 1; i < pseudoTeamsQ; i++) { var iOld = teamRoundList[round - 1][i] if (iOld == pseudoTeamsQ - 1) iNew = pseudoTeamsQ - 2 else if (iOld == 2) iNew = 1 else iNew = iOld % 2 ? iOld + 2 : iOld - 2 teamRoundList[round].push(iNew) } } // Game list league.games = [] for (var round = 1; round < roundQ + 1; round++) { for (var i = 0; i < pseudoTeamsQ; i += 2) { // Pairs of teams in flat list — teamRoundList[round] — are separated into matches // In odd rounds, first team in pair is host, and the second one is guest // In even rounds, second team in pair is host, and the first one is guest var homeTeamId = teamRoundList[round][i + 1 - round % 2] var awayTeamId = teamRoundList[round][i + round % 2] // Check if this is not a bye (in case of odd number of teams in league) if (homeTeamId < leagueTeamQ && awayTeamId < leagueTeamQ) { league.games.push({ round: round, // Home and away teams team: [league.teams[homeTeamId], league.teams[awayTeamId]], // Goals of home and away teams score: [0, 0], // Status: played — 0 (no) or 1 (yes) played: 0, // Status: overtime — 0 (no) or 1 (yes) overtime: 0, }) } } } } function playLeagueGames(league) { league.games.forEach(runGame) } function makeLeagueStandings(league) { // Initial standings league.standings = [] // Going through all teams in league league.teams.forEach(function(team, t) { team.level = league.level // Datarow of the given team in league table var standingsRow = { team: team, w: 0, ow: 0, ol: 0, d: 0, l: 0, gf: 0, ga: 0, points: 0 } // Look through home and away games for (var isAway = 0; isAway < 2; isAway++) { league.games.filter(function(a) { return a.team[isAway] == team }).forEach(function(game, g) { // Wins and losses if (game.score[isAway] > game.score[1 - isAway]) { if (game.overtime) standingsRow.ow++ else standingsRow.w++ } else if (game.score[isAway] < game.score[1 - isAway]) { if (game.overtime) standingsRow.ol++ else standingsRow.l++ } else standingsRow.d++ // Goals standingsRow.gf += game.score[isAway] standingsRow.ga += game.score[1 - isAway] }) } // Goal difference standingsRow.gd = standingsRow.gf - standingsRow.ga // Points standingsRow.points = points.w * standingsRow.w + points.ow * standingsRow.ow + points.d * standingsRow.d + points.ol * standingsRow.ol // Adding the team datarow to the league table league.standings.push(standingsRow) }) // Sorting of league table league.standings.sort(sortStandings) } function sortStandings(a, b) { // Sort the teams in league standings by leagueSortingParameters: var response = 0 $.each(leagueSortingParameters, function(i, f) { response = b[f] - a[f] if (response) return false }) return response } function doPromotionsRelegations() { for (var level = 0; level < leagueLevelQ; level++) { // Empty list of teams at a given level League[level].teams = [] // Teams relegated from a league above for (var i = 0; i < (level ? exchangeTeamQ : 0); i++) { League[level].teams.push( League[level - 1].standings[leagueTeamQ - exchangeTeamQ + i].team ) } // Middle zone of a given league for (var i = (level ? exchangeTeamQ : 0); i < (level == leagueLevelQ - 1 ? leagueTeamQ : leagueTeamQ - exchangeTeamQ); i++) { League[level].teams.push( League[level].standings[i].team ) } // Teams promoted from a league below for (var i = (level == leagueLevelQ - 1 ? exchangeTeamQ : 0); i < exchangeTeamQ; i++) { League[level].teams.push( League[level + 1].standings[i].team ) } // Shuffle the teams in league // Though, it's not the most needed action, as an order of team in standings is already not like an original order of teams in league // League[level].teams.sort(shuffle) } } function seasonReset() { var strategiesKeys = Object.keys(strategies) // Teams (except teams with "user function") from relegation zone of the lowest league change their strategies for (var t = leagueTeamQ - 1; t > leagueTeamQ - 1 - exchangeTeamQ; t--) { var strategyKey = strategiesKeys[getRandomInt(1, strategiesKeys.length - 1)] if (League[leagueLevelQ - 1].standings[t].team.strategyName !== 'userfunc') { League[leagueLevelQ - 1].standings[t].team.strategyName = strategyKey League[leagueLevelQ - 1].standings[t].team.decision = strategies[strategyKey] } } // Renew teams editor makeTeamControls() } // ———————————————————————————————————————————————————————————————————————————————————————————————————————————— // DISPLAY THE RESULTS function showSeasonStatistics() { // General season wrapper var html = $('#tpl-season').html().replace(/%season%/g, season) $('#seasonalStatistics').prepend(html) // Deleting old history if ($('#seasonalStatistics .season-wrapper').length > 100) { $('#seasonalStatistics .season-wrapper').slice(50).remove() } // League wrapper for (var league = 1; league < leagueLevelQ + 1; league++) { var html = $('#tpl-league').html().replace(/%season%/g, season).replace(/%league%/g, league) $('#S' + season).append(html) // Games wrapper showLeagueGames(League[league - 1]) // Standings wrapper showLeagueStandings(League[league - 1]) } // Refreshing jQuery UI accordion, activation of the panel of last season $('#seasonalStatistics').accordion('refresh') $('#seasonalStatistics').accordion('option', 'active', 0) showGames() showStandings() } function showLeagueGames(league) { // Show the table of games of the given league var tpl = $('#tpl-game').html() var tplSeparator = $('#tpl-round-separator').html() var html = '' var prevRound = 0 // For round separator league.games.forEach(function(game, g) { if (prevRound != game.round) html += tplSeparator.replace(/%round%/g, game.round) var teamdata = ['', ''] for (var i = 0; i < 2; i++) teamdata[i] = game.stat.t[i] + '\n' var gamedata = 'DD: ' + game.stat.s[0][0] + '; DC: ' + game.stat.s[0][1] + '; CD: ' + game.stat.s[1][0] + '; CC: ' + game.stat.s[1][1] html += tpl.replace(/%round%/g, game.round) .replace(/%hometeam%/g, game.team[0].name) .replace(/%awayteam%/g, game.team[1].name) .replace(/%homegoals%/g, game.score[0]) .replace(/%awaygoals%/g, game.score[1]) .replace(/%ot%/g, game.overtime ? 'OT' : '') .replace(/%hometeamdata%/g, teamdata[0]) .replace(/%awayteamdata%/g, teamdata[1]) .replace(/%gamedata%/g, gamedata) prevRound = game.round }) $('#S' + season + '-L' + league.level + '-games tbody').html(html) } function showLeagueStandings(league) { // Show the standings of the given league var tpl = $('#tpl-standings').html() var html = '' league.standings.forEach(function(row, r) { var trClass = r == 0 ? 'promotion champion' : r < exchangeTeamQ ? 'promotion' : r >= leagueTeamQ - exchangeTeamQ ? 'relegation' : '' html += tpl.replace(/%trClass%/g, trClass) .replace(/%pos%/g, r + 1) .replace(/%team%/g, row.team.name + (row.team.titles ? ' ' + row.team.titles + "Ψ" : '')) .replace(/%w%/g, row.w) .replace(/%ow%/g, row.ow) .replace(/%d%/g, row.d) .replace(/%ol%/g, row.ol) .replace(/%l%/g, row.l) .replace(/%gf%/g, row.gf) .replace(/%ga%/g, row.ga) .replace(/%gd%/g, row.gf - row.ga) .replace(/%pts%/g, row.points) .replace(/%other%/g, row.team.strategyName) }) $('#S' + season + '-L' + league.level + '-standings tbody').html(html) } // ———————————————————————————————————————————————————————————————————————————————————————————————————————————— // WORLD AND SEASONS function initWorld() { // Season 0 — team list and league structure have to be created season = 0 createTeamList() createLeagueStructure() // Previous results have to be deleted $('#seasonalStatistics').html('') } function runSeason() { // Just in case of parallel run if (seasonCalculationOn) return false seasonCalculationOn = true season++ for (var level = 0; level < leagueLevelQ; level++) { // Actions inside each league: Schedule, games, standings var league = League[level] makeLeagueSchedule(league) playLeagueGames(league) makeLeagueStandings(league) } // Title counter League[0].standings[0].team.titles++ // Statistics and team exchange showSeasonStatistics() doPromotionsRelegations() seasonReset() seasonCalculationOn = false } // ———————————————————————————————————————————————————————————————————————————————————————————————————————————— // WHEN THE DOCUMENT IS READY // $(function() { // initWorld() // runSeason() // }) // ———————————————————————————————————————————————————————————————————————————————————————————————————————————— // MINI FUNCTIONS // For array shuffling function shuffle(){ return 0.5 - Math.random() } // Random integer within the range [min, max] (range includes both limits) function getRandomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min } // ———————————————————————————————————————————————————————————————————————————————————————————————————————————— // TEAM CONTROLS function makeTeamControls() { var tpl = $('#tpl-team-strategy').html() var html = '' teams.forEach(function(team, t) { var options = '' Object.keys(strategies).forEach(function(key) { var selected = key == team.strategyName ? 'selected' : '' options += '' }) html += tpl.replace(/%team-name%/g, team.name) .replace(/%team-id%/g, t) .replace(/%options%/g, options) .replace(/%level%/g, team.level) }) $('#teams tbody').html(html) } // ———————————————————————————————————————————————————————————————————————————————————————————————————————————— // GAME FUNCTION function runGame(game) { // Statistics — preparation game.stat = { s: [[0, 0], [0, 0]], // number of decisions of each type t: [game.team[0].strategyName, game.team[1].strategyName] } // "Fight" starts with empty memories of both opponents game.team[0].memory = [] game.team[1].memory = [] var rounds = getRandomInt(roundsMin, roundsMax) for (var r = 0; r < rounds; r++) { var d1 = game.team[0].decision() var d2 = game.team[1].decision() if (d1 !== 0 && d1 !== 1) d1 = getRandomInt(0, 1) if (d2 !== 0 && d2 !== 1) d2 = getRandomInt(0, 1) game.score[0] += ipdPoints[d1][d2] game.score[1] += ipdPoints[d2][d1] game.team[0].memory.unshift(d2) game.team[1].memory.unshift(d1) // Statistics game.stat.s[d1][d2]++ } // Status "game is played" game.played = 1 }
// Author: Oleksa Vyshnivsky a.k.a ODE // al_d@ukr.net // ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // Iterational calculation var intervalId = null var timeStep = 60 // ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // Active correction of variables function makeControlForm() { $('#points-w').val(points.w) // $('#points-ow').val(points.ow) $('#points-d').val(points.d) // $('#points-ol').val(points.ol) // $('#points-l').val(points.l) $('#ipdPoints-0-0').val(ipdPoints[0][0]) $('#ipdPoints-0-1').val(ipdPoints[0][1]) $('#ipdPoints-1-0').val(ipdPoints[1][0]) $('#ipdPoints-1-1').val(ipdPoints[1][1]) $('#roundsMin').val(roundsMin) $('#roundsMax').val(roundsMax) $.each(leagueSortingParameters, function(i, parameter) { $('#leagueSortingParameters').append('