Space Invasion: Continue playing rounds in case the teams are tied at the end
authorWuzzy <Wuzzy2@mail.ru>
Tue, 28 Aug 2018 05:46:33 +0200
changeset 13711 0da36902e5b6
parent 13710 e2b1abb4fba5
child 13712 d5f12b1d180d
Space Invasion: Continue playing rounds in case the teams are tied at the end Rules in case of a tie: 1) Eliminate all teams not tied for the lead 2) Play another round with the remaining teams 3) Check for the winner again at the end of that round. If there's another tie, repeat the procedure
ChangeLog.txt
share/hedgewars/Data/Scripts/Multiplayer/Space_Invasion.lua
--- a/ChangeLog.txt	Tue Aug 28 03:45:44 2018 +0200
+++ b/ChangeLog.txt	Tue Aug 28 05:46:33 2018 +0200
@@ -67,6 +67,7 @@
 Space Invasion:
  + Display round score in a separate row
  + Keep round score displayed after round ends, remove round score announcer message
+ + If team scores are tied at the end, continue playing rounds with the tied teams until there's a winner
  * Fix rare Lua error message spam at end of game
  * Fix round score and other info numbers messing up after screen resize
  * Fix kamikaze bonus not being shown
--- a/share/hedgewars/Data/Scripts/Multiplayer/Space_Invasion.lua	Tue Aug 28 03:45:44 2018 +0200
+++ b/share/hedgewars/Data/Scripts/Multiplayer/Space_Invasion.lua	Tue Aug 28 05:46:33 2018 +0200
@@ -89,8 +89,6 @@
 
 -- add support for other world edges (they are currently disabled)
 
--- maybe add a check for a tie, IMPOSSIBRU THERE ARE NO TIES
-
 -- if more weapons are added, replace primshotsfired all over the place
 
 -- look for derp and let invaders shoot again
@@ -148,13 +146,10 @@
 SI.preciseOn = false
 SI.roundLimit = 3		-- can be overridden by script parameter "rounds"
 SI.roundNumber = 0
-SI.firstClan = 10
+SI.lastRound = -1
 SI.gameOver = false
 SI.gameBegun = false
 
-SI.bestClan = 65535
-SI.bestScore = 0
-
 -- for script parameters
 -- NOTE: If you change this, also change the default “Space Invasion” game scheme
 SI.startBarrels = 5		-- "barrels"
@@ -172,8 +167,8 @@
 SI.numhhs = 0
 SI.hhs = {}
 
-SI.numTeams = 0
 SI.teamNameArr = {}
+SI.teamNameArrReverse = {}
 SI.teamClan = {}
 SI.teamSize = {}
 SI.teamIndex = {}
@@ -390,33 +385,12 @@
 		SI.teamCircsKilled[i] = 0
 		SI.teamSurfer[i] = false
 	end
-	SI.numTeams = 0
-
-	for i = 0, (SI.numhhs-1) do
-
-		local z = 0
-		local unfinished = true
-		while(unfinished == true) do
-
-			local newTeam = true
-			local tempHogTeamName = GetHogTeamName(SI.hhs[i]) -- this is the new name
-
-			if tempHogTeamName == SI.teamNameArr[z] then
-				newTeam = false
-				unfinished = false
-			end
-
-			z = z + 1
-
-			if z == (TeamsCount-1) then
-				unfinished = false
-				if newTeam == true then
-					SI.teamNameArr[SI.numTeams] = tempHogTeamName
-					SI.numTeams = SI.numTeams + 1
-				end
-			end
-
-		end
+
+	for i=0, TeamsCount-1 do
+
+		local name = GetTeamName(i)
+		SI.teamNameArr[i] = name
+		SI.teamNameArrReverse[name] = i
 
 	end
 
@@ -500,6 +474,11 @@
 	return newAward
 end
 
+-- Update scoreboard and check victory state.
+-- Returns 2 bools:
+-- 1: true if game over
+-- 2: true if game's not over but we're playing now in tie-breaking phase
+
 function CommentOnScore()
 	local teamStats = {}
 	for i = 0,(TeamsCount-1) do
@@ -521,7 +500,7 @@
 			local comment = teamStats[i].name .. " |" ..
 			string.format(loc("Score: %d"), teamStats[i].score) .. "|" ..
 			string.format(loc("Kills: %d"), teamStats[i].kills)
-			if i < TeamsCount then	
+			if i < TeamsCount then
 				comment = comment .. "| |"
 			end
 			table.insert(teamComment, comment)
@@ -533,20 +512,40 @@
 		table.insert(teamComment, comment)
 	end
 
+	local roundLimitHit = SI.roundNumber >= SI.roundLimit
+	local tie = teamStats[1].score == teamStats[2].score
+	local lGameOver = roundLimitHit and (not tie)
+
 	local entireC = ""
+
 	for i = TeamsCount,1,-1 do
 		entireC = entireC .. teamComment[i]
 	end
+
 	local statusText, scoreText
-	if SI.roundNumber >= SI.roundLimit then
+	-- Game is over
+	if lGameOver then
 		statusText = loc("Game over!")
 		scoreText = loc("Final team scores:")
-	else
+	-- Round is over and game is not yet complete
+	elseif not roundLimitHit then
 		AddCaption(string.format(loc("Rounds complete: %d/%d"), SI.roundNumber, SI.roundLimit), capcolDefault, capgrpMessage)
-		return
+		return lGameOver, false
+	-- Teams are tied for the lead at the end
+	elseif roundLimitHit and tie then
+		local tieBreakingRound = SI.roundNumber - SI.roundLimit + 1
+		local msg
+		if tieBreakingRound == 1 then
+			msg = loc("Teams are tied! Continue playing rounds until we have a winner!")
+		else
+			msg = string.format(loc("Tie-breaking round %d"), tieBreakingRound)
+		end
+		AddCaption(msg, capcolDefault, capgrpMessage)
+		return lGameOver, true
 	end
+
 	local displayTime
-	if SI.roundNumber >= SI.roundLimit then
+	if lGameOver then
 		displayTime = 20000
 	else
 		displayTime = 1
@@ -556,7 +555,7 @@
 			string.format(loc("Rounds complete: %d/%d"), SI.roundNumber, SI.roundLimit) .. "| " .. "|" ..
 			scoreText .. " |" ..entireC, 4, displayTime)
 
-	if SI.roundNumber >= SI.roundLimit then
+	if lGameOver then
 		local winnerTeam = teamStats[1].name
 		for i = 0, (SI.numhhs-1) do
 			if GetHogTeamName(SI.hhs[i]) == winnerTeam then
@@ -583,7 +582,17 @@
 --[[ Award some awards (just for fun, its for the stats screen only
 and has no effect on the score or game outcome. ]]
 		local awardsGiven = 0
-	
+
+		if SI.roundNumber == SI.roundLimit + 1 then
+			SendStat(siCustomAchievement,
+			loc("The teams were tied, so an additional round has been played to determine the winner."))
+			awardsGiven = awardsGiven + 1
+		elseif SI.roundNumber > SI.roundLimit then
+			SendStat(siCustomAchievement,
+			string.format(loc("The teams were tied, so %d additional rounds have been played to determine the winner."),
+			SI.roundNumber - SI.roundLimit))
+			awardsGiven = awardsGiven + 1
+		end
 		if SI.awardTotalKills >= 30 then
 			awardsGiven = awardsGiven + 1
 			SendStat(siCustomAchievement,
@@ -683,30 +692,36 @@
 			elseif r == 3 then text = loc("Nothing of interest has happened.")
 			elseif r == 4 then text = loc("There are no snarky comments this time.")
 			end
-		
+
 			SendStat(siCustomAchievement, text)
 		end
 	end
+
+	return lGameOver, false
 end
 
 function onNewRound()
+	SI.lastRound = TotalRounds
 	SI.roundNumber = SI.roundNumber + 1
 
-	CommentOnScore()
-
-	-- end game if its at round limit
-	if SI.roundNumber >= SI.roundLimit then
-
+	local lGameOver, lTied = CommentOnScore()
+	local bestScore = 0
+	local bestClan = -1
+
+	-- Game has been determined to be over, so end it
+	if lGameOver then
+
+		-- Get winning score
 		for i = 0, (TeamsCount-1) do
-			if SI.teamScore[i] > SI.bestScore then
-				SI.bestScore = SI.teamScore[i]
-				SI.bestClan = SI.teamClan[i]
+			if SI.teamScore[i] > bestScore then
+				bestScore = SI.teamScore[i]
+				bestClan = SI.teamClan[i]
 			end
 		end
 
 		-- Kill off all the losers
 		for i = 0, (SI.numhhs-1) do
-			if GetHogClan(SI.hhs[i]) ~= SI.bestClan then
+			if GetHogClan(SI.hhs[i]) ~= bestClan then
 				SetEffect(SI.hhs[i], heResurrectable, 0)
 				SetHealth(SI.hhs[i],0)
 			end
@@ -717,13 +732,66 @@
 		EndTurn(true)
 		SI.TimeLeft = 0
 		SendStat(siGraphTitle, loc("Score graph"))
+
+	-- Round limit passed and teams are tied!
+	elseif lTied then
+		-- Enter (or continue) tie-breaking phase...
+
+		-- Rules in case of a tie:
+		-- 1) All teams that are not tied for the lead are killed (they can't play anymore, but they will keep their score and be ranked normally)
+		-- 2) Another round is played with the remaining teams
+		-- 3) After this round, scores are checked again to determine a winner. If there's a tie again, this procedure is repeated
+
+		-- Get leading teams
+		for i = 0, (TeamsCount-1) do
+			if SI.teamScore[i] > bestScore then
+				bestScore = SI.teamScore[i]
+			end
+		end
+
+		local tiedForTheLead = {}
+		for i = 0, (TeamsCount-1) do
+			if SI.teamScore[i] == bestScore then
+				tiedForTheLead[i] = true
+			end
+		end
+
+		local wasCurrent = false
+		-- Kill teams not in the top
+		for i = 0, (SI.numhhs-1) do
+			local hog = SI.hhs[i]
+			if GetHealth(hog) then -- check if hog is still alive
+				local team = SI.teamNameArrReverse[GetHogTeamName(hog)]
+				if team and tiedForTheLead[team] ~= true then
+					-- hilarious loser face
+					SetState(hog, bor(GetState(hog), gstLoser))
+					-- die!
+					SetEffect(hog, heResurrectable, 0)
+					SetHealth(hog, 0)
+					-- Note the death might not trigger immediately since we
+					-- zero the health at the beginning of a turn rather than
+					-- the end of one.
+					-- It's just a minor visual thing, not a big deal.
+					if hog == CurrentHedgehog then
+						wasCurrent = true
+					end
+				end
+			end
+		end
+
+		-- if current hedgehog was among the loser, end the turn
+		if wasCurrent then
+			EndTurn(true)
+		end
+
+		-- From that point on, the game just continues normally ...
 	end
 end
 
 -- gaudy racer
 function CheckForNewRound()
 
-	if GetHogClan(CurrentHedgehog) == SI.firstClan then
+	if TotalRounds > 0 and TotalRounds > SI.lastRound then
 		onNewRound()
 	end
 
@@ -1108,7 +1176,6 @@
 	if (SI.gameOver == false) and (SI.gameBegun == false) then
 		SI.gameBegun = true
 		SI.roundNumber = 0 -- 0
-		SI.firstClan = GetHogClan(CurrentHedgehog)
 	end
 
 	if SI.gameOver == true then
@@ -1303,7 +1370,7 @@
 							value = SI.shotsHit, 
 						}
 					end
-		
+
 				end
 
 				-- other awards