contrib/tinks: add the server
authorAlexis Laferrière <alexis.laf@xymus.net>
Fri, 3 Jul 2015 21:14:36 +0000 (17:14 -0400)
committerAlexis Laferrière <alexis.laf@xymus.net>
Sun, 2 Aug 2015 16:25:23 +0000 (12:25 -0400)
Signed-off-by: Alexis Laferrière <alexis.laf@xymus.net>

contrib/tinks/src/common.nit [new file with mode: 0644]
contrib/tinks/src/server/dedicated.nit [new file with mode: 0644]
contrib/tinks/src/server/server.nit [new file with mode: 0644]

diff --git a/contrib/tinks/src/common.nit b/contrib/tinks/src/common.nit
new file mode 100644 (file)
index 0000000..8aae159
--- /dev/null
@@ -0,0 +1,29 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Services common to the client and server
+module common
+
+import gamnit::network
+
+redef class Sys
+       # Name of this app
+       redef fun handshake_app_name do return "tinks"
+
+       # Default listening port of the server
+       fun default_listening_port: Int do return 18721
+
+       # Port to which clients send discovery requests
+       fun discovery_port: Int do return 18722
+end
diff --git a/contrib/tinks/src/server/dedicated.nit b/contrib/tinks/src/server/dedicated.nit
new file mode 100644 (file)
index 0000000..db7bd9c
--- /dev/null
@@ -0,0 +1,24 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Dedicated server program
+module dedicated
+
+import server
+
+var port = sys.default_listening_port
+if args.length > 0 then port = args[0].to_i
+
+var server = new Server(port)
+server.run_dedicated
diff --git a/contrib/tinks/src/server/server.nit b/contrib/tinks/src/server/server.nit
new file mode 100644 (file)
index 0000000..a74b727
--- /dev/null
@@ -0,0 +1,131 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Server to host multiplayer games
+module server
+
+import gamnit::network
+
+import game
+import common
+
+redef class RemoteClient
+       # `Player` associated to this client
+       var player = new Player
+end
+
+redef class Server
+       # `UDPSocket` to which clients send discovery requests
+       var discovery_socket: UDPSocket do
+               var s = new UDPSocket
+               s.blocking = false
+               s.bind(null, discovery_port)
+               return s
+       end
+
+       # The current game
+       var game = new TGame is lazy, writable
+
+       # Is this a dedicated server
+       var dedicated = false
+
+       # Create and run a new `Game`
+       fun run_dedicated
+       do
+               dedicated = true
+
+               # Setup game
+               print "Server: Setup game"
+               game
+
+               # Play
+               print "Server: Starting play"
+               loop do_turn
+       end
+
+       # Run the server logic over a single turn
+       fun do_turn: TTurn
+       do
+               var game = game
+
+               # Do game logic
+               var turn = game.do_turn
+
+               # Respond to discovery requests
+               loop
+                       var ptr = new Ref[nullable SocketAddress](null)
+                       var read = discovery_socket.recv_from(1024, ptr)
+
+                       # No sender means there is no request (an error would also do it)
+                       var sender = ptr.item
+                       if sender == null then break
+
+                       var words = read.split(" ")
+                       if words.length != 2 or words[0] != "Server?" or words[1] != handshake_app_name then
+                               print "Server Warning: Rejected discovery request '{read}'"
+                               continue
+                       end
+
+                       discovery_socket.send_to(sender.address, sender.port,
+                               "Server! {handshake_app_name} {self.port}")
+               end
+
+               # Setup clients
+               var new_clients = accept_clients
+               for client in new_clients do
+                       # Register player and spawn first tank
+                       game.players.add client.player
+                       turn.spawn_tank client.player
+
+                       client.writer.serialize game
+                       client.writer.serialize client.player
+                       client.socket.flush
+
+                       clients.add client
+               end
+
+               if dedicated and clients.is_empty then
+                       # No clients, sleep for a while
+                       nanosleep(0, 10000000)
+                       return turn
+               end
+
+               # Update clients
+               broadcast turn
+
+               # Get orders from players
+               var clients_to_remove = new Array[RemoteClient]
+               for client in clients do
+                       var orders = client.reader.deserialize
+                       var errors = client.reader.errors
+                       if errors.not_empty then
+                               print_error "Comm Error: (Dropping client) {errors.join(", ")}"
+                               clients_to_remove.add client
+                       else if not orders isa Array[TOrder] then
+                               if orders == null then
+                                       print_error "Comm Error: (Dropping client) Unexpected null"
+                               else print_error "Comm Error: (Dropping client) Unexpected {orders.class_name}"
+                               # TODO remove code duplication when we have ? or an equivalent
+
+                               clients_to_remove.add client
+                       else
+                               client.player.orders.add_all orders
+                       end
+               end
+
+               for client in clients_to_remove do clients.remove client
+
+               return turn
+       end
+end