Skip to content

Commit edd84eb

Browse files
author
p.witvoet
committed
- implemented basic situation update system (no filtering for fog-of-war yet, no broadcasting to clients)
1 parent a34face commit edd84eb

18 files changed

+778
-146
lines changed

documentation/notes.txt

+62-1
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,65 @@ In normal mode, just send all units, all data.
106106
Oh, situation update should also tell the player how much money he has.
107107

108108

109-
A full situation update, towards observers, should tell them how much money every player has.
109+
A full situation update, towards observers, should tell them how much money every player has.
110+
111+
112+
113+
114+
115+
116+
117+
118+
119+
120+
server: has multiple clientPlayerControllers (RENAME to clientController!)
121+
122+
123+
124+
right now, the game tells a player that it's his turn.
125+
126+
it's clientPlayerController is listening for callbacks,
127+
and will be informed when it can start - it will send
128+
a message to it's client.
129+
130+
clientPlayerControllers react to AI client input by
131+
calling methods on their player, and they send a message
132+
back based on the result.
133+
134+
The main code will intercept incoming messages however,
135+
if they're sent by a client who can't move (it's not his turn yet).
136+
137+
138+
the player functions will return a (result, Diff) tuple,
139+
where Diff is a SituationDiff object that contains a number
140+
of lists: added units, modified units, removed units.
141+
142+
NOTE: How to deal with changed state? only position updates and hiding/
143+
unhiding are particularly important, hmm? we need to know the previous
144+
state somehow!!!
145+
146+
perhaps only keep track of previous and current position,
147+
and let clients do the rest of the diffing? so clients are
148+
only updated about units that have disappeared from their
149+
original location and have reappeared somewhere else (or
150+
have disappeared only, or appeared only).
151+
152+
153+
154+
So, build a UnitUpdate object as a return value for every command.
155+
NOTE: Some commands affect multiple units! Attack commands, for example,
156+
can leave both units damaged (but not moved). Movement commands can
157+
move multiple units at once (transported units)!
158+
159+
160+
161+
162+
The server code receives a SituationUpdate from the command that it called,
163+
and it then asks the game to give a player-specific SituationUpdate for
164+
each player - which it will send to those player clients. The unfiltered
165+
SituationUpdate will be sent to all observers.
166+
167+
This player-specific SituationUpdate should include all opponent units/buildings
168+
that have become visible or hidden! I should write a utility function in the
169+
Player class for this - one that updates the visibility/stealth detection maps
170+
and that extracts a list of new visible and no-longer-visible units/buildings.

run server.bat

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@ TITLE AI Wars - Server
22

33
CD source
44

5-
run_server.py localhost 7777 central_island.aws aiwars.py
5+
python run_server.py localhost 7777 river_bridges.aws aiwars.py
66

77
PAUSE

source/core/building.py

+20-11
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1+
import copy
12
from buildingType import *
23
from serialization import *
34

45

56
class Building(object):
6-
def __init__(self, game, gameDatabase, type, id, position, player):
7+
def __init__(self, game, type, id, position, player):
78
self.game = game
8-
self.gameDatabase = gameDatabase
99
self.type = type
1010
self.id = id
1111
self.position = position
1212

1313
self.player = player
1414

15-
self.capturePoints = self.type.maxCapturePoints
15+
self.capturePoints = self.type.maxCapturePoints if self.type != None else 20
1616
#
1717

1818
def canBuild(self, unitType):
@@ -33,22 +33,31 @@ def isCritical(self):
3333

3434
#Some units can capture buildings. This function returns true if the building has been fully captured.
3535
def capture(self, amount):
36-
self.capturePoints = max(0, self.capturePoints - amount)
36+
self.capturePoints = int(max(0, self.capturePoints - amount))
3737
return self.capturePoints == 0
3838
#
3939

4040
def restoreCapturePoints(self):
4141
self.capturePoints = self.type.maxCapturePoints
4242
#
4343

44+
# Special method, called when making a shallow copy
45+
def __copy__(self):
46+
building = Building(self.game, self.type, self.id, copy.copy(self.position), self.player)
47+
48+
building.capturePoints = self.capturePoints
49+
50+
return building
51+
#
52+
4453

4554
# Serialization
46-
def toStream(self):
47-
playerID = -1
55+
def toStream(self, hideInformation):
56+
playerID = 0
4857
if self.player != None:
4958
playerID = self.player.id
5059

51-
return toStream(self.gameDatabase.getIndexOfBuildingType(self.type), \
60+
return toStream(self.game.gameDatabase.getIndexOfBuildingType(self.type), \
5261
self.id, \
5362
self.position.x, \
5463
self.position.y, \
@@ -57,16 +66,16 @@ def toStream(self):
5766
#
5867

5968
def fromStream(self, stream):
60-
(self.type, \
69+
(typeIndex, \
6170
self.id, \
6271
self.position.x, \
6372
self.position.y, \
64-
self.player, \
73+
playerID, \
6574
self.capturePoints, \
6675
readBytesCount) = fromStream(stream, int, int, int, int, int, int)
6776

68-
self.type = self.gameDatabase.getBuildingType(self.type)
69-
self.player = self.game.getPlayerByID(self.player)
77+
self.type = self.game.gameDatabase.getBuildingType(typeIndex)
78+
self.player = self.game.getPlayerByID(playerID)
7079

7180
return readBytesCount
7281
#

source/core/buildingUpdate.py

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import copy
2+
from building import *
3+
from serialization import *
4+
5+
6+
# A building update stores a copy of the building and a reference to it.
7+
# So if you create a BuildingUpdate before modifying the building,
8+
# the update will allow you to compare between the previous and current state.
9+
# For newly captured building, just pass None, and set newBuilding manually.
10+
# Likewise, for lost buildings, pass None and set oldBuilding manually.
11+
class BuildingUpdate(object):
12+
def __init__(self, game, building):
13+
self.game = game
14+
15+
self.oldBuilding = copy.copy(building) if building != None else None
16+
self.newBuilding = building
17+
#
18+
19+
def buildingID(self):
20+
return self.oldBuilding.id if self.oldBuilding != None else self.newBuilding.id
21+
#
22+
23+
24+
def toStream(self, hideInformation):
25+
stream = toStream(self.oldBuilding.id if self.oldBuilding != None else 0, self.newBuilding.id if self.newBuilding != None else 0)
26+
27+
stream += self.__buildingToStream(self.oldBuilding, hideInformation)
28+
stream += self.__buildingToStream(self.newBuilding, hideInformation)
29+
30+
return stream
31+
#
32+
33+
def __buildingToStream(self, building, hideInformation):
34+
if building == None:
35+
return ''
36+
else:
37+
return building.toStream(hideInformation)
38+
#
39+
40+
def fromStream(self, stream):
41+
oldBuildingID, newBuildingID, totalReadBytesCount = fromStream(stream, int, int)
42+
43+
self.oldBuilding, readBytesCount = self.__buildingFromStream(stream[totalReadBytesCount:], oldBuildingID)
44+
totalReadBytesCount += readBytesCount
45+
self.newBuilding, readBytesCount = self.__buildingFromStreamFromStream(stream[totalReadBytesCount:], newBuildingID)
46+
totalReadBytesCount += readBytesCount
47+
48+
return totalReadBytesCount
49+
#
50+
51+
def __buildingFromStream(self, stream, buildingID):
52+
if buildingID == 0:
53+
return (None, 0)
54+
else:
55+
building = Building(self.game.gameDatabase, None, 0, Point(0, 0), None)
56+
readBytesCount = building.fromStream(stream)
57+
return (building, readBytesCount)
58+
#
59+
#

source/core/clientPlayerController.py

+22-10
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,23 @@ def setPlayer(self, player):
2727
#
2828

2929

30+
#================================================================================
31+
# Messages sent to the client
32+
33+
# Player callbacks
3034
def onPlayerStartsTurn(self, player):
31-
self.client.sendMessage(STC_START_TURN, '')
35+
if player == self.player:
36+
self.client.sendMessage(STC_START_TURN, '')
3237
#
3338

3439
def onPlayerEndsTurn(self, player):
35-
self.client.sendMessage(STC_END_TURN, '')
40+
if player == self.player:
41+
self.client.sendMessage(STC_END_TURN, '')
42+
#
43+
44+
def sendSituationUpdate(self, players, level):
45+
# TODO!!!
46+
pass
3647
#
3748

3849

@@ -68,55 +79,56 @@ def onMoveCommand(self, message):
6879
(unitID, route, readBytesCount) = fromStream(message, int, list)
6980
route = self.__intListToPointList(route)
7081

71-
result = self.player.moveUnit(unitID, route)
82+
(result, situationUpdate) = self.player.moveUnit(unitID, route)
7283
self.client.sendMessage(STC_RESULT, resultToMessage[result])
84+
self.client.sendMessage(STC_SITUATION_UPDATE, '''TODO!!!''') # Check if new units became visible!
7385
#
7486

7587
def onUnloadCommand(self, message):
7688
(unitID, destinationX, destinationY, readBytesCount) = fromStream(message, int, int, int)
7789
destination = Point(destinationX, destinationY)
7890

79-
result = self.player.unloadUnit(unitID, destination)
91+
(result, situationUpdate) = self.player.unloadUnit(unitID, destination)
8092
self.client.sendMessage(STC_RESULT, resultToMessage[result])
8193
#
8294

8395
def onSupplySurroundingUnitsCommand(self, message):
8496
(unitID, readBytesCount) = fromStream(message, int)
8597

86-
result = self.player.supplySurroundingUnits(unitID)
98+
(result, situationUpdate) = self.player.supplySurroundingUnits(unitID)
8799
self.client.sendMessage(STC_RESULT, resultToMessage[result])
88100
#
89101

90102
def onAttackUnitCommand(self, message):
91103
(unitID, targetID, readBytesCount) = fromStream(message, int, int)
92104

93-
result = self.player.attackUnit(unitID, targetID)
105+
(result, situationUpdate) = self.player.attackUnit(unitID, targetID)
94106
self.client.sendMessage(STC_RESULT, resultToMessage[result])
95107
#
96108

97109
def onBuildUnitCommand(self, message):
98110
(buildingID, unitTypeID, readBytesCount) = fromStream(message, int, int)
99111

100-
result = self.player.buildUnit(buildingID, unitTypeID)
112+
(result, situationUpdate) = self.player.buildUnit(buildingID, unitTypeID)
101113
self.client.sendMessage(STC_RESULT, resultToMessage[result])
102114
#
103115

104116
def onCaptureBuildingCommand(self, message):
105117
(unitID, readBytesCount) = fromStream(message, int)
106118

107-
result = self.player.captureBuilding(unitID)
119+
(result, situationUpdate) = self.player.captureBuilding(unitID)
108120
self.client.sendMessage(STC_RESULT, resultToMessage[result])
109121
#
110122

111123
def onHideUnitCommand(self, message):
112124
(unitID, hide, readBytesCount) = fromStream(message, int, bool)
113125

114-
result = self.player.hideUnit(unitID, hide)
126+
(result, situationUpdate) = self.player.hideUnit(unitID, hide)
115127
self.client.sendMessage(STC_RESULT, resultToMessage[result])
116128
#
117129

118130
def onEndTurnCommand(self, message):
119-
result = self.player.endTurn()
131+
(result, situationUpdate) = self.player.endTurn()
120132
self.client.sendMessage(STC_RESULT, resultToMessage[result])
121133
#
122134

source/core/game.py

+1-5
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,7 @@ def removePlayer(self, player):
2727
#
2828

2929
def getOtherPlayers(self, player):
30-
otherPlayers = []
31-
for otherPlayer in self.players:
32-
if otherPlayer is not player:
33-
otherPlayers.append(otherPlayer)
34-
return otherPlayers
30+
return [p for p in self.players if p != player]
3531
#
3632

3733
def getAllPlayers(self):

source/core/guid.py

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
__guid = 0
44

5+
# Never return 0 - this allows using 0 as a special case!
56
def getGUID():
67
global __guid
78
__guid += 1

source/core/level.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def setTileData(self, tileData):
5858
self.terrain[y][x] = terrainType
5959

6060
if terrainType.buildingType != None:
61-
self.addBuilding(Building(self.game, self.gameDatabase, terrainType.buildingType, getGUID(), Point(x, y), None))
61+
self.addBuilding(Building(self.game, terrainType.buildingType, getGUID(), Point(x, y), None))
6262
#
6363

6464
def addBuilding(self, building):
@@ -114,7 +114,7 @@ def toStream(self):
114114

115115
buildingsStream = ''
116116
for building in self.buildings:
117-
buildingsStream += building.toStream()
117+
buildingsStream += building.toStream(False)
118118

119119
return toStream(self.name, \
120120
self.author, \
@@ -138,7 +138,7 @@ def fromStream(self, stream):
138138
self.buildings = []
139139
for i in xrange(buildingsCount):
140140
# TODO: Instead of having to create a Building instance with some default type, construct one directly from a stream?
141-
building = Building(self.game, self.gameDatabase, self.gameDatabase.getBuildingType(0), 0, Point(0, 0), None)
141+
building = Building(self.game, self.gameDatabase.getBuildingType(0), 0, Point(0, 0), None)
142142
readBytesCount += building.fromStream(stream[readBytesCount:])
143143
self.buildings.append(building)
144144

source/core/messageTypes.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
CTS_CAPTURE_BUILDING = chr(8) # Captures a building
1616
CTS_HIDE_UNIT = chr(9) # Hides or unhides the unit
1717
CTS_END_TURN = chr(10) # Signals that this client is done with it's turn
18+
# TODO: Add a 'destroy unit' command?
1819

1920

2021
#================================================================================
@@ -29,14 +30,13 @@
2930
# While the game is going
3031
STC_START_TURN = chr(4) # Tells a client that it is it's turn
3132
STC_END_TURN = chr(5) # Tells a client that it's turn is over
32-
STC_SITUATION_UPDATE = chr(6) # Sends the latest situation data to the client - only what this client can see!
33+
STC_SITUATION_UPDATE = chr(6) # Sends situation changes to the client - only changes that this client can see! (observer clients can always see every change)
3334
STC_RESULT = chr(7) # Sends the result of the latest command, see SERVER_RESULT_...
3435
STC_END_GAME = chr(8) # Informs clients that the game is over
3536

3637
# While the game is going, observer-specific messages
3738
STC_PLAYER_STARTED_TURN = chr(9) # Tells an observer which players turn has started
3839
STC_PLAYER_ENDED_TURN = chr(10) # Tells an observer which players turn has ended
39-
STC_FULL_SITUATION_UPDATE = chr(11) # Complete situation update - observers know everything
4040

4141

4242
#================================================================================

0 commit comments

Comments
 (0)