- 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('