Add MacMahon 3.9 import support
- Add MacMahon39.kt parser for MM39 tournament format - Auto-detect MM39 format in tournament import - Import players, games, bye players, and tournament parameters - Uses default values for time system and director since MM39 lacks those
This commit is contained in:
@@ -5,6 +5,7 @@ import com.republicate.kson.toJsonObject
|
|||||||
import com.republicate.kson.toMutableJsonObject
|
import com.republicate.kson.toMutableJsonObject
|
||||||
import org.jeudego.pairgoth.api.ApiHandler.Companion.PAYLOAD_KEY
|
import org.jeudego.pairgoth.api.ApiHandler.Companion.PAYLOAD_KEY
|
||||||
import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest
|
import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest
|
||||||
|
import org.jeudego.pairgoth.ext.MacMahon39
|
||||||
import org.jeudego.pairgoth.ext.OpenGotha
|
import org.jeudego.pairgoth.ext.OpenGotha
|
||||||
import org.jeudego.pairgoth.model.BaseCritParams
|
import org.jeudego.pairgoth.model.BaseCritParams
|
||||||
import org.jeudego.pairgoth.model.TeamTournament
|
import org.jeudego.pairgoth.model.TeamTournament
|
||||||
@@ -55,7 +56,7 @@ object TournamentHandler: PairgothApiHandler {
|
|||||||
override fun post(request: HttpServletRequest, response: HttpServletResponse): Json? {
|
override fun post(request: HttpServletRequest, response: HttpServletResponse): Json? {
|
||||||
val tournament = when (val payload = request.getAttribute(PAYLOAD_KEY)) {
|
val tournament = when (val payload = request.getAttribute(PAYLOAD_KEY)) {
|
||||||
is Json.Object -> Tournament.fromJson(getObjectPayload(request))
|
is Json.Object -> Tournament.fromJson(getObjectPayload(request))
|
||||||
is Element -> OpenGotha.import(payload)
|
is Element -> if (MacMahon39.isFormat(payload)) MacMahon39.import(payload) else OpenGotha.import(payload)
|
||||||
else -> badRequest("missing or invalid payload")
|
else -> badRequest("missing or invalid payload")
|
||||||
}
|
}
|
||||||
tournament.recomputeDUDD()
|
tournament.recomputeDUDD()
|
||||||
|
|||||||
@@ -0,0 +1,258 @@
|
|||||||
|
package org.jeudego.pairgoth.ext
|
||||||
|
|
||||||
|
import org.jeudego.pairgoth.model.*
|
||||||
|
import org.jeudego.pairgoth.store.nextGameId
|
||||||
|
import org.jeudego.pairgoth.store.nextPlayerId
|
||||||
|
import org.jeudego.pairgoth.store.nextTournamentId
|
||||||
|
import org.w3c.dom.Element
|
||||||
|
import org.w3c.dom.Node
|
||||||
|
import org.w3c.dom.NodeList
|
||||||
|
import java.time.LocalDate
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MacMahon 3.9 format import support
|
||||||
|
* Ported from OpenGothaCustom (https://bitbucket.org/kamyszyn/opengothacustom)
|
||||||
|
*/
|
||||||
|
object MacMahon39 {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the XML element is in MacMahon 3.9 format
|
||||||
|
*/
|
||||||
|
fun isFormat(element: Element): Boolean {
|
||||||
|
val tournament = element.getElementsByTagName("Tournament").item(0) ?: return false
|
||||||
|
val typeVersion = tournament.attributes?.getNamedItem("typeversion")?.nodeValue
|
||||||
|
return typeVersion != null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import a MacMahon 3.9 format tournament
|
||||||
|
*/
|
||||||
|
fun import(element: Element): Tournament<*> {
|
||||||
|
val tournamentEl = element.getElementsByTagName("Tournament").item(0) as? Element
|
||||||
|
?: throw Error("No Tournament element found")
|
||||||
|
|
||||||
|
// Parse tournament settings
|
||||||
|
val name = extractValue("Name", tournamentEl, "Tournament")
|
||||||
|
val numberOfRounds = extractValue("NumberOfRounds", tournamentEl, "5").toInt()
|
||||||
|
val mmBarStr = extractValue("UpperMacMahonBarLevel", tournamentEl, "1d")
|
||||||
|
val mmFloorStr = extractValue("LowerMacMahonBarLevel", tournamentEl, "30k")
|
||||||
|
val isMMBar = extractValue("UpperMacMahonBar", tournamentEl, "true") == "true"
|
||||||
|
val isMMFloor = extractValue("LowerMacMahonBar", tournamentEl, "true") == "true"
|
||||||
|
val handicapUsed = extractValue("HandicapUsed", tournamentEl, "false").equals("true", ignoreCase = true)
|
||||||
|
val handicapByRank = extractValue("HandicapByLevel", tournamentEl, "false").equals("true", ignoreCase = true)
|
||||||
|
val handicapBelowStr = extractValue("HandicapBelowLevel", tournamentEl, "30k")
|
||||||
|
val isHandicapBelow = extractValue("HandicapBelow", tournamentEl, "true").equals("true", ignoreCase = true)
|
||||||
|
val handicapCorrectionStr = extractValue("HandicapAdjustmentValue", tournamentEl, "0")
|
||||||
|
val isHandicapReduction = extractValue("HandicapAdjustment", tournamentEl, "true").equals("true", ignoreCase = true)
|
||||||
|
val handicapCeilingStr = extractValue("HandicapLimitValue", tournamentEl, "9")
|
||||||
|
val isHandicapLimit = extractValue("HandicapLimit", tournamentEl, "true").equals("true", ignoreCase = true)
|
||||||
|
|
||||||
|
// Parse placement criteria from Walllist
|
||||||
|
val walllistEl = element.getElementsByTagName("Walllist").item(0) as? Element
|
||||||
|
val breakers = if (walllistEl != null) extractValues("ShortName", walllistEl) else listOf("Score", "SOS", "SOSOS")
|
||||||
|
|
||||||
|
// Determine effective values
|
||||||
|
val mmBar = if (isMMBar) parseRank(mmBarStr) else 8 // 9d
|
||||||
|
val mmFloor = if (isMMFloor) parseRank(mmFloorStr) else -30 // 30k
|
||||||
|
val handicapBelow = if (isHandicapBelow) parseRank(handicapBelowStr) else 8 // 9d
|
||||||
|
val handicapCorrection = if (isHandicapReduction) -1 * handicapCorrectionStr.toInt() else 0
|
||||||
|
val handicapCeiling = when {
|
||||||
|
!handicapUsed -> 0
|
||||||
|
!isHandicapLimit -> 30
|
||||||
|
else -> handicapCeilingStr.toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create pairing parameters
|
||||||
|
val pairingParams = PairingParams(
|
||||||
|
base = BaseCritParams(),
|
||||||
|
main = MainCritParams(),
|
||||||
|
secondary = SecondaryCritParams(),
|
||||||
|
geo = GeographicalParams(),
|
||||||
|
handicap = HandicapParams(
|
||||||
|
useMMS = !handicapByRank,
|
||||||
|
rankThreshold = handicapBelow,
|
||||||
|
correction = handicapCorrection,
|
||||||
|
ceiling = handicapCeiling
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create placement parameters from breakers
|
||||||
|
val placementCrit = breakers.take(6).mapNotNull { translateBreaker(it, breakers.firstOrNull() == "Points") }.toTypedArray()
|
||||||
|
val placementParams = PlacementParams(crit = if (placementCrit.isEmpty()) arrayOf(Criterion.MMS, Criterion.SOSM, Criterion.SOSOSM) else placementCrit)
|
||||||
|
|
||||||
|
// Create tournament
|
||||||
|
val tournament = StandardTournament(
|
||||||
|
id = nextTournamentId,
|
||||||
|
type = Tournament.Type.INDIVIDUAL,
|
||||||
|
name = name,
|
||||||
|
shortName = name.take(20),
|
||||||
|
startDate = LocalDate.now(),
|
||||||
|
endDate = LocalDate.now(),
|
||||||
|
director = "",
|
||||||
|
country = "",
|
||||||
|
location = "",
|
||||||
|
online = false,
|
||||||
|
timeSystem = SuddenDeath(3600), // Default: 1 hour sudden death
|
||||||
|
pairing = MacMahon(
|
||||||
|
pairingParams = pairingParams,
|
||||||
|
placementParams = placementParams,
|
||||||
|
mmFloor = mmFloor,
|
||||||
|
mmBar = mmBar
|
||||||
|
),
|
||||||
|
rounds = numberOfRounds
|
||||||
|
)
|
||||||
|
|
||||||
|
// Parse players
|
||||||
|
val playerIdMap = mutableMapOf<String, ID>()
|
||||||
|
val goPlayers = element.getElementsByTagName("GoPlayer")
|
||||||
|
for (i in 0 until goPlayers.length) {
|
||||||
|
val playerEl = goPlayers.item(i) as? Element ?: continue
|
||||||
|
val parentEl = playerEl.parentNode as? Element ?: continue
|
||||||
|
|
||||||
|
val mm39Id = extractValue("Id", parentEl, "1")
|
||||||
|
val egfPin = extractValue("EgdPin", playerEl, "").let { if (it.length < 8) "" else it }
|
||||||
|
val firstname = extractValue("FirstName", playerEl, " ")
|
||||||
|
val surname = extractValue("Surname", playerEl, " ")
|
||||||
|
val club = extractValue("Club", playerEl, "")
|
||||||
|
val country = extractValue("Country", playerEl, "").uppercase()
|
||||||
|
val rankStr = extractValue("GoLevel", playerEl, "30k")
|
||||||
|
val rank = parseRank(rankStr)
|
||||||
|
val ratingStr = extractValue("Rating", playerEl, "-901")
|
||||||
|
val rating = ratingStr.toInt()
|
||||||
|
val superBarMember = extractValue("SuperBarMember", parentEl, "false") == "true"
|
||||||
|
val preliminary = extractValue("PreliminaryRegistration", parentEl, "false") == "true"
|
||||||
|
|
||||||
|
val player = Player(
|
||||||
|
id = nextPlayerId,
|
||||||
|
name = surname,
|
||||||
|
firstname = firstname,
|
||||||
|
rating = rating,
|
||||||
|
rank = rank,
|
||||||
|
country = if (country == "GB") "UK" else country,
|
||||||
|
club = club,
|
||||||
|
final = !preliminary,
|
||||||
|
mmsCorrection = if (superBarMember) 1 else 0
|
||||||
|
).also {
|
||||||
|
if (egfPin.isNotEmpty()) {
|
||||||
|
it.externalIds[DatabaseId.EGF] = egfPin
|
||||||
|
}
|
||||||
|
// Parse not playing rounds
|
||||||
|
val notPlayingRounds = extractValues("NotPlayingInRound", parentEl)
|
||||||
|
for (roundStr in notPlayingRounds) {
|
||||||
|
val round = roundStr.toIntOrNull() ?: continue
|
||||||
|
it.skip.add(round)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
playerIdMap[mm39Id] = player.id
|
||||||
|
tournament.players[player.id] = player
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse games (pairings)
|
||||||
|
val pairings = element.getElementsByTagName("Pairing")
|
||||||
|
for (i in 0 until pairings.length) {
|
||||||
|
val pairingEl = pairings.item(i) as? Element ?: continue
|
||||||
|
val parentEl = pairingEl.parentNode as? Element ?: continue
|
||||||
|
|
||||||
|
val isByeGame = extractValue("PairingWithBye", pairingEl, "false").equals("true", ignoreCase = true)
|
||||||
|
val roundNumber = extractValue("RoundNumber", parentEl, "1").toInt()
|
||||||
|
val boardNumber = extractValue("BoardNumber", pairingEl, "${i + 1}").toInt()
|
||||||
|
|
||||||
|
if (isByeGame) {
|
||||||
|
// Bye player
|
||||||
|
val blackId = extractValue("Black", pairingEl, "")
|
||||||
|
val playerId = playerIdMap[blackId] ?: continue
|
||||||
|
val game = Game(
|
||||||
|
id = nextGameId,
|
||||||
|
table = 0,
|
||||||
|
white = playerId,
|
||||||
|
black = 0,
|
||||||
|
result = Game.Result.WHITE
|
||||||
|
)
|
||||||
|
tournament.games(roundNumber)[game.id] = game
|
||||||
|
} else {
|
||||||
|
// Regular game
|
||||||
|
val whiteId = extractValue("White", pairingEl, "")
|
||||||
|
val blackId = extractValue("Black", pairingEl, "")
|
||||||
|
val whitePId = playerIdMap[whiteId] ?: continue
|
||||||
|
val blackPId = playerIdMap[blackId] ?: continue
|
||||||
|
val handicap = extractValue("Handicap", pairingEl, "0").toInt()
|
||||||
|
val resultStr = extractValue("Result", pairingEl, "?-?")
|
||||||
|
val resultByRef = extractValue("ResultByReferee", pairingEl, "false").equals("true", ignoreCase = true)
|
||||||
|
|
||||||
|
val game = Game(
|
||||||
|
id = nextGameId,
|
||||||
|
table = boardNumber,
|
||||||
|
white = whitePId,
|
||||||
|
black = blackPId,
|
||||||
|
handicap = handicap,
|
||||||
|
result = parseResult(resultStr, resultByRef)
|
||||||
|
)
|
||||||
|
tournament.games(roundNumber)[game.id] = game
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tournament
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper functions
|
||||||
|
|
||||||
|
private fun extractValue(tag: String, element: Element, default: String): String {
|
||||||
|
return try {
|
||||||
|
val nodes = element.getElementsByTagName(tag).item(0)?.childNodes
|
||||||
|
nodes?.item(0)?.nodeValue ?: default
|
||||||
|
} catch (e: Exception) {
|
||||||
|
default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun extractValues(tag: String, element: Element): List<String> {
|
||||||
|
val result = mutableListOf<String>()
|
||||||
|
try {
|
||||||
|
val nodeList = element.getElementsByTagName(tag)
|
||||||
|
for (i in 0 until minOf(nodeList.length, 20)) {
|
||||||
|
val nodes = nodeList.item(i)?.childNodes
|
||||||
|
nodes?.item(0)?.nodeValue?.let { result.add(it) }
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseRank(rankStr: String): Int {
|
||||||
|
val regex = Regex("(\\d+)([kKdD])")
|
||||||
|
val match = regex.matchEntire(rankStr) ?: return -20
|
||||||
|
val (num, letter) = match.destructured
|
||||||
|
val level = num.toIntOrNull() ?: return -20
|
||||||
|
return when (letter.lowercase()) {
|
||||||
|
"k" -> -level
|
||||||
|
"d" -> level - 1
|
||||||
|
else -> -20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseResult(resultStr: String, byRef: Boolean): Game.Result {
|
||||||
|
// MM39 result format: "1-0" (white wins), "0-1" (black wins), etc.
|
||||||
|
// The format uses black-first convention (first number is black's score)
|
||||||
|
return when (resultStr.removeSuffix("!")) {
|
||||||
|
"0-1" -> Game.Result.WHITE
|
||||||
|
"1-0" -> Game.Result.BLACK
|
||||||
|
"\u00BD-\u00BD" -> Game.Result.JIGO
|
||||||
|
"0-0" -> Game.Result.BOTHLOOSE
|
||||||
|
"1-1" -> Game.Result.BOTHWIN
|
||||||
|
else -> Game.Result.UNKNOWN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateBreaker(breaker: String, swiss: Boolean): Criterion? {
|
||||||
|
return when (breaker) {
|
||||||
|
"Points" -> Criterion.NBW
|
||||||
|
"Score", "ScoreX" -> Criterion.MMS
|
||||||
|
"SOS" -> if (swiss) Criterion.SOSW else Criterion.SOSM
|
||||||
|
"SOSOS" -> if (swiss) Criterion.SOSOSW else Criterion.SOSOSM
|
||||||
|
"SODOS" -> if (swiss) Criterion.SODOSW else Criterion.SODOSM
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
package org.jeudego.pairgoth.test
|
package org.jeudego.pairgoth.test
|
||||||
|
|
||||||
|
import org.jeudego.pairgoth.ext.MacMahon39
|
||||||
import org.jeudego.pairgoth.ext.OpenGotha
|
import org.jeudego.pairgoth.ext.OpenGotha
|
||||||
import org.jeudego.pairgoth.model.toJson
|
import org.jeudego.pairgoth.model.toJson
|
||||||
import org.jeudego.pairgoth.util.XmlUtils
|
import org.jeudego.pairgoth.util.XmlUtils
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class ImportExportTests: TestBase() {
|
class ImportExportTests: TestBase() {
|
||||||
|
|
||||||
@@ -56,4 +58,73 @@ class ImportExportTests: TestBase() {
|
|||||||
assertEquals(jsonTournament, jsonTournament2)
|
assertEquals(jsonTournament, jsonTournament2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `003 test macmahon39 import`() {
|
||||||
|
getTestResources("macmahon39")?.forEach { file ->
|
||||||
|
logger.info("===== Testing MacMahon 3.9 import: ${file.name} =====")
|
||||||
|
val resource = file.readText(StandardCharsets.UTF_8)
|
||||||
|
val root_xml = XmlUtils.parse(resource)
|
||||||
|
|
||||||
|
// Verify format detection
|
||||||
|
assertTrue(MacMahon39.isFormat(root_xml), "File should be detected as MacMahon 3.9 format")
|
||||||
|
|
||||||
|
// Import tournament
|
||||||
|
val tournament = MacMahon39.import(root_xml)
|
||||||
|
|
||||||
|
// Verify basic tournament data
|
||||||
|
logger.info("Tournament name: ${tournament.name}")
|
||||||
|
logger.info("Number of rounds: ${tournament.rounds}")
|
||||||
|
logger.info("Number of players: ${tournament.pairables.size}")
|
||||||
|
|
||||||
|
assertEquals("Test MacMahon Tournament", tournament.name)
|
||||||
|
assertEquals(3, tournament.rounds)
|
||||||
|
assertEquals(4, tournament.pairables.size)
|
||||||
|
|
||||||
|
// Verify players
|
||||||
|
val players = tournament.pairables.values.toList()
|
||||||
|
val alice = players.find { it.name == "Smith" }
|
||||||
|
val bob = players.find { it.name == "Jones" }
|
||||||
|
val carol = players.find { it.name == "White" }
|
||||||
|
val david = players.find { it.name == "Brown" }
|
||||||
|
|
||||||
|
assertTrue(alice != null, "Alice should exist")
|
||||||
|
assertTrue(bob != null, "Bob should exist")
|
||||||
|
assertTrue(carol != null, "Carol should exist")
|
||||||
|
assertTrue(david != null, "David should exist")
|
||||||
|
|
||||||
|
assertEquals(2, alice!!.rank) // 3d = rank 2
|
||||||
|
assertEquals(1, bob!!.rank) // 2d = rank 1
|
||||||
|
assertEquals(0, carol!!.rank) // 1d = rank 0
|
||||||
|
assertEquals(-1, david!!.rank) // 1k = rank -1
|
||||||
|
|
||||||
|
// Carol is super bar member
|
||||||
|
assertEquals(1, carol.mmsCorrection)
|
||||||
|
|
||||||
|
// David skips round 2
|
||||||
|
assertTrue(david.skip.contains(2), "David should skip round 2")
|
||||||
|
|
||||||
|
// Verify games
|
||||||
|
val round1Games = tournament.games(1).values.toList()
|
||||||
|
val round2Games = tournament.games(2).values.toList()
|
||||||
|
|
||||||
|
logger.info("Round 1 games: ${round1Games.size}")
|
||||||
|
logger.info("Round 2 games: ${round2Games.size}")
|
||||||
|
|
||||||
|
assertEquals(2, round1Games.size)
|
||||||
|
assertEquals(2, round2Games.size) // 1 regular game + 1 bye
|
||||||
|
|
||||||
|
// Test via API
|
||||||
|
val resp = TestAPI.post("/api/tour", resource)
|
||||||
|
val id = resp.asObject().getInt("id")
|
||||||
|
logger.info("Imported tournament id: $id")
|
||||||
|
|
||||||
|
val apiTournament = TestAPI.get("/api/tour/$id").asObject()
|
||||||
|
assertEquals("Test MacMahon Tournament", apiTournament.getString("name"))
|
||||||
|
assertEquals(3, apiTournament.getInt("rounds"))
|
||||||
|
|
||||||
|
val apiPlayers = TestAPI.get("/api/tour/$id/part").asArray()
|
||||||
|
assertEquals(4, apiPlayers.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
132
api-webapp/src/test/resources/macmahon39/sample-tournament.xml
Normal file
132
api-webapp/src/test/resources/macmahon39/sample-tournament.xml
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Sample MacMahon 3.9 format tournament file for testing -->
|
||||||
|
<TournamentData typeversion="3.9">
|
||||||
|
<Tournament typeversion="3.9">
|
||||||
|
<Name>Test MacMahon Tournament</Name>
|
||||||
|
<NumberOfRounds>3</NumberOfRounds>
|
||||||
|
<UpperMacMahonBar>true</UpperMacMahonBar>
|
||||||
|
<UpperMacMahonBarLevel>1d</UpperMacMahonBarLevel>
|
||||||
|
<LowerMacMahonBar>true</LowerMacMahonBar>
|
||||||
|
<LowerMacMahonBarLevel>20k</LowerMacMahonBarLevel>
|
||||||
|
<RatingDeterminesRank>false</RatingDeterminesRank>
|
||||||
|
<HandicapUsed>false</HandicapUsed>
|
||||||
|
<HandicapBelow>true</HandicapBelow>
|
||||||
|
<HandicapBelowLevel>30k</HandicapBelowLevel>
|
||||||
|
<HandicapAdjustment>true</HandicapAdjustment>
|
||||||
|
<HandicapAdjustmentValue>0</HandicapAdjustmentValue>
|
||||||
|
<HandicapLimit>true</HandicapLimit>
|
||||||
|
<HandicapLimitValue>9</HandicapLimitValue>
|
||||||
|
<HandicapByLevel>false</HandicapByLevel>
|
||||||
|
</Tournament>
|
||||||
|
|
||||||
|
<Walllist>
|
||||||
|
<Criterion><ShortName>Score</ShortName></Criterion>
|
||||||
|
<Criterion><ShortName>SOS</ShortName></Criterion>
|
||||||
|
<Criterion><ShortName>SOSOS</ShortName></Criterion>
|
||||||
|
</Walllist>
|
||||||
|
|
||||||
|
<Playerlist>
|
||||||
|
<Player>
|
||||||
|
<Id>1</Id>
|
||||||
|
<PreliminaryRegistration>false</PreliminaryRegistration>
|
||||||
|
<SuperBarMember>false</SuperBarMember>
|
||||||
|
<GoPlayer>
|
||||||
|
<FirstName>Alice</FirstName>
|
||||||
|
<Surname>Smith</Surname>
|
||||||
|
<Club>Paris</Club>
|
||||||
|
<Country>FR</Country>
|
||||||
|
<GoLevel>3d</GoLevel>
|
||||||
|
<Rating>2200</Rating>
|
||||||
|
<EgdPin>12345678</EgdPin>
|
||||||
|
</GoPlayer>
|
||||||
|
</Player>
|
||||||
|
<Player>
|
||||||
|
<Id>2</Id>
|
||||||
|
<PreliminaryRegistration>false</PreliminaryRegistration>
|
||||||
|
<SuperBarMember>false</SuperBarMember>
|
||||||
|
<GoPlayer>
|
||||||
|
<FirstName>Bob</FirstName>
|
||||||
|
<Surname>Jones</Surname>
|
||||||
|
<Club>Lyon</Club>
|
||||||
|
<Country>FR</Country>
|
||||||
|
<GoLevel>2d</GoLevel>
|
||||||
|
<Rating>2100</Rating>
|
||||||
|
<EgdPin>23456789</EgdPin>
|
||||||
|
</GoPlayer>
|
||||||
|
</Player>
|
||||||
|
<Player>
|
||||||
|
<Id>3</Id>
|
||||||
|
<PreliminaryRegistration>false</PreliminaryRegistration>
|
||||||
|
<SuperBarMember>true</SuperBarMember>
|
||||||
|
<GoPlayer>
|
||||||
|
<FirstName>Carol</FirstName>
|
||||||
|
<Surname>White</Surname>
|
||||||
|
<Club>Berlin</Club>
|
||||||
|
<Country>DE</Country>
|
||||||
|
<GoLevel>1d</GoLevel>
|
||||||
|
<Rating>2000</Rating>
|
||||||
|
<EgdPin>34567890</EgdPin>
|
||||||
|
</GoPlayer>
|
||||||
|
</Player>
|
||||||
|
<Player>
|
||||||
|
<Id>4</Id>
|
||||||
|
<PreliminaryRegistration>true</PreliminaryRegistration>
|
||||||
|
<SuperBarMember>false</SuperBarMember>
|
||||||
|
<NotPlayingInRound>2</NotPlayingInRound>
|
||||||
|
<GoPlayer>
|
||||||
|
<FirstName>David</FirstName>
|
||||||
|
<Surname>Brown</Surname>
|
||||||
|
<Club>London</Club>
|
||||||
|
<Country>UK</Country>
|
||||||
|
<GoLevel>1k</GoLevel>
|
||||||
|
<Rating>1900</Rating>
|
||||||
|
<EgdPin>45678901</EgdPin>
|
||||||
|
</GoPlayer>
|
||||||
|
</Player>
|
||||||
|
</Playerlist>
|
||||||
|
|
||||||
|
<Roundlist>
|
||||||
|
<Round>
|
||||||
|
<RoundNumber>1</RoundNumber>
|
||||||
|
<Pairing>
|
||||||
|
<BoardNumber>1</BoardNumber>
|
||||||
|
<PairingWithBye>false</PairingWithBye>
|
||||||
|
<White>1</White>
|
||||||
|
<Black>2</Black>
|
||||||
|
<Handicap>0</Handicap>
|
||||||
|
<Result>1-0</Result>
|
||||||
|
<ResultByReferee>false</ResultByReferee>
|
||||||
|
</Pairing>
|
||||||
|
<Pairing>
|
||||||
|
<BoardNumber>2</BoardNumber>
|
||||||
|
<PairingWithBye>false</PairingWithBye>
|
||||||
|
<White>3</White>
|
||||||
|
<Black>4</Black>
|
||||||
|
<Handicap>0</Handicap>
|
||||||
|
<Result>0-1</Result>
|
||||||
|
<ResultByReferee>false</ResultByReferee>
|
||||||
|
</Pairing>
|
||||||
|
</Round>
|
||||||
|
<Round>
|
||||||
|
<RoundNumber>2</RoundNumber>
|
||||||
|
<Pairing>
|
||||||
|
<BoardNumber>1</BoardNumber>
|
||||||
|
<PairingWithBye>false</PairingWithBye>
|
||||||
|
<White>2</White>
|
||||||
|
<Black>1</Black>
|
||||||
|
<Handicap>0</Handicap>
|
||||||
|
<Result>0-1</Result>
|
||||||
|
<ResultByReferee>false</ResultByReferee>
|
||||||
|
</Pairing>
|
||||||
|
<Pairing>
|
||||||
|
<BoardNumber>0</BoardNumber>
|
||||||
|
<PairingWithBye>true</PairingWithBye>
|
||||||
|
<White>0</White>
|
||||||
|
<Black>3</Black>
|
||||||
|
<Handicap>0</Handicap>
|
||||||
|
<Result>0-1</Result>
|
||||||
|
<ResultByReferee>false</ResultByReferee>
|
||||||
|
</Pairing>
|
||||||
|
</Round>
|
||||||
|
</Roundlist>
|
||||||
|
</TournamentData>
|
||||||
Reference in New Issue
Block a user