1 module mjtournament.tournament;
2 
3 enum size_t playerPerTable = 4;
4 import std.algorithm, std.range, std.array, std.string, std.format, std.conv, std.random;
5 
6 struct Tournament
7 {
8 	immutable size_t tables, players, rounds;
9 	size_t[][] assignment;
10 	pure this (in size_t tables, in size_t players, in size_t rounds)
11 	{
12 		this.tables = tables;
13 		this.players = players;
14 		this.rounds = rounds;
15 		this.assignment = new size_t[][] (players, rounds);
16 	}
17 	size_t[][] incidenceMatrix()
18 	{
19 		auto ret = new size_t[][] (players, players);
20 		foreach (i, p; assignment)
21 			foreach (j, q; assignment)
22 				if (i == j)
23 					break;
24 				else
25 					foreach (r; 0..rounds)
26 						ret[j][i] = ret[i][j] += p[r] == q[r] && p[r] < tables;
27 		return ret;
28 	}
29 	void toString(scope void delegate(const(char)[]) sink,
30 		FormatSpec!char fs) const
31 	{
32 		switch (fs.spec)
33 		{
34 			case 's', 'p': // default (player-centered) format
35 				foreach (player; assignment)
36 					sink("%(%d %)\n".format(player));
37 				break;
38 			case 't': // table-centered format
39 				foreach (round; 0..rounds)
40 				{
41 					foreach (table; 0..tables)
42 						sink("%(%d %)\n".format(players.iota.filter!(player => assignment[player][round] == table)));
43 					if (round + 1 < rounds)
44 						sink("\n");
45 				}
46 				break;
47 			case 'l': // one-liner format
48 				sink("%d %d %d".format(tables, players, rounds));
49 				foreach (round; 0..rounds)
50 					foreach (player; 0..players)
51 						sink(" %d".format(assignment[player][round]));
52 				break;
53 			default:
54 				throw new Exception("Unknown format specifier: %" ~ fs.spec);
55 		}
56 	}
57 }
58 
59 pure Tournament skipRounds(Tournament tournament, size_t skip)
60 in
61 {
62 	assert (skip <= tournament.rounds);
63 }
64 body
65 {
66 	auto ret = Tournament(tournament.tables, tournament.players, tournament.rounds - skip);
67 	ret.assignment = tournament.assignment.map!(a => a[skip..$]).array;
68 	return ret;
69 }
70 
71 
72 Tournament scramble(in Tournament tournament, in size_t teamSize)
73 {
74 	assert (tournament.players % teamSize == 0);
75 	auto teams = tournament.players / teamSize;
76 	auto ret = Tournament(tournament.tables, tournament.players, tournament.rounds);
77 	size_t p;
78 	foreach (team; teams.iota.randomCover)
79 		foreach (idx; teamSize.iota.randomCover)
80 			ret.assignment[p++] = tournament.assignment[team * teamSize + idx].dup;
81 	return ret;
82 }
83 
84 Tournament scrambleTables(Tournament tournament)
85 {
86 	auto ret = Tournament(tournament.tables, tournament.players, tournament.rounds);
87 	assert (false);
88 }
89 
90 /** Read a tournament from one-liner string.
91 
92 Input line must be a sequence of non-negative integers separated by white spaces, which start with numbers of tables, players, and rounds.
93 The following (players * rounds) numbers are interpreted as the tables at which players play in the rounds of the tournament.
94 
95 example:
96 toTournament("1 5 5 [1 0 0 0 0] [0 1 0 0 0] [0 0 1 0 0] [0 0 0 1 0] [0 0 0 0 1]") (brackets for explanation)
97 is a trivial tournament played by one table with 5 players.
98 Each section in the brackets is a round, each element of which is the number of the table each player plays at.
99 
100 */
101 pure Tournament toTournament(string x)
102 {
103 	auto buf = x.strip.split(",")[0].split.map!(to!size_t);
104 	auto tournament = Tournament(buf[0], buf[1], buf[2]);
105 	buf = buf[3..$];
106 	foreach (round; 0..tournament.rounds)
107 		foreach (player; 0..tournament.players)
108 		{
109 			tournament.assignment[player][round] = buf.front;
110 			buf.popFront;
111 		}
112 	return tournament;
113 }
114 
115 /** Read a tournament from a string array.
116 
117 Each line corresponds to a player and is indicating the tables at which the player plays in the rounds.
118 */
119 pure Tournament toTournament(string[] x)
120 {
121 	x = x.filter!(e => e.length).array;
122 	immutable
123 		players = x.length,
124 		tables = players / playerPerTable;
125 	auto buf = x.map!(e => e.strip.split.map!(to!size_t).array).array;
126 	immutable rounds = buf[0].length;
127 	auto tournament = Tournament(tables, players, rounds);
128 	foreach (player, assignment; buf)
129 		foreach (round, table; assignment)
130 			tournament.assignment[player][round] = table;
131 	return tournament;
132 }
133 
134 
135 
136