Compare commits
7 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
8e31de0793 | ||
![]() |
8cba94e081 | ||
![]() |
4ce40ee01d | ||
![]() |
b00ab9b38a | ||
![]() |
df0fcb5952 | ||
![]() |
bbd9151d7f | ||
![]() |
9de3c7586d |
11 changed files with 77 additions and 61 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -1,2 +1,8 @@
|
||||||
# Ignore tex files
|
# Ignore tex files
|
||||||
*.tex
|
*.tex
|
||||||
|
|
||||||
|
# Ignore projects files
|
||||||
|
**.vscode
|
||||||
|
|
||||||
|
# Ignore cache files
|
||||||
|
**__pycache__
|
||||||
|
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"python.pythonPath": "/usr/bin/python"
|
|
||||||
}
|
|
Binary file not shown.
|
@ -1,3 +1,4 @@
|
||||||
|
#!/usr/bin/env python2.7
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import socket, json, time
|
import socket, json, time
|
||||||
import pygame
|
import pygame
|
||||||
|
@ -6,25 +7,19 @@ import pygame
|
||||||
|
|
||||||
RESP_BUFFER_LENGTH = 1024
|
RESP_BUFFER_LENGTH = 1024
|
||||||
ip_adress="192.168.1.14"
|
ip_adress="192.168.1.14"
|
||||||
port=8090
|
port=8080
|
||||||
|
|
||||||
LARGEUR_BLOCK = 10
|
LARGEUR_BLOCK = 10
|
||||||
NB_BLOCKS = 30
|
NB_BLOCKS = 30
|
||||||
LARGEUR_ECRAN = LARGEUR_BLOCK * NB_BLOCKS
|
LARGEUR_ECRAN = LARGEUR_BLOCK * NB_BLOCKS
|
||||||
|
|
||||||
def connect():
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
||||||
s.connect((ip_adress,port))
|
|
||||||
return s
|
|
||||||
|
|
||||||
def sendData(data):
|
def sendData(data):
|
||||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
s.sendto(data,(ip_adress,port))
|
||||||
s.connect((ip_adress,port))
|
received = s.recvfrom(1024)
|
||||||
s.sendall(data.encode('utf-8'))
|
return received[0]
|
||||||
s.send('#EOF\n'.encode('utf-8'))
|
|
||||||
received = s.recv(RESP_BUFFER_LENGTH)
|
|
||||||
s.close()
|
|
||||||
return received
|
|
||||||
|
|
||||||
def newGame():
|
def newGame():
|
||||||
received = sendData('{"type": "new-game"}')
|
received = sendData('{"type": "new-game"}')
|
||||||
|
@ -39,6 +34,7 @@ def update(gameId = 1, direction = None):
|
||||||
received = sendData(json.dumps(data))
|
received = sendData(json.dumps(data))
|
||||||
return json.loads(received)
|
return json.loads(received)
|
||||||
|
|
||||||
|
|
||||||
# ---------- END SOCKETS ----------
|
# ---------- END SOCKETS ----------
|
||||||
|
|
||||||
LARGEUR_BLOCK = 20
|
LARGEUR_BLOCK = 20
|
||||||
|
@ -47,7 +43,7 @@ LARGEUR_ECRAN = LARGEUR_BLOCK * NB_BLOCKS
|
||||||
|
|
||||||
pygame.init()
|
pygame.init()
|
||||||
ecran = pygame.display.set_mode((LARGEUR_ECRAN,LARGEUR_ECRAN))
|
ecran = pygame.display.set_mode((LARGEUR_ECRAN,LARGEUR_ECRAN))
|
||||||
|
myfont = pygame.font.SysFont('Comic Sans MS', 20)
|
||||||
snakeSprite = pygame.image.load("snake-sprite.png").convert_alpha()
|
snakeSprite = pygame.image.load("snake-sprite.png").convert_alpha()
|
||||||
snakeSprite = pygame.transform.scale(snakeSprite, (LARGEUR_BLOCK, LARGEUR_BLOCK))
|
snakeSprite = pygame.transform.scale(snakeSprite, (LARGEUR_BLOCK, LARGEUR_BLOCK))
|
||||||
|
|
||||||
|
@ -81,28 +77,29 @@ def handleControls(event, gameId):
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
gameInit = newGame()
|
gameInit = newGame()
|
||||||
gameId = gameInit['ID']
|
gameId = gameInit['id']
|
||||||
up = gameInit
|
up = gameInit
|
||||||
|
initSize=len(up['snake'])
|
||||||
continuer = True
|
continuer = True
|
||||||
|
|
||||||
while continuer:
|
while continuer:
|
||||||
afficher(ecran, up['SNAKE'], up['FOOD'])
|
afficher(ecran, up['snake'], up['food'])
|
||||||
|
ecran.blit(myfont.render('Score {}'.format(len(up['snake'])-initSize), False, (0, 0, 0)),(0,0))
|
||||||
for event in pygame.event.get():
|
for event in pygame.event.get():
|
||||||
if event.type == pygame.QUIT:
|
if event.type == pygame.QUIT:
|
||||||
continuer = False
|
continuer = False
|
||||||
elif event.type == pygame.KEYDOWN:
|
elif event.type == pygame.KEYDOWN:
|
||||||
updateTmp = handleControls(event, gameId)
|
updateTmp = handleControls(event, gameId)
|
||||||
print(updateTmp)
|
print(updateTmp)
|
||||||
if updateTmp != None and 'TYPE' in updateTmp:
|
if updateTmp != None and 'type' in updateTmp:
|
||||||
if updateTmp['TYPE'] != 'error':
|
if updateTmp['type'] != 'error':
|
||||||
up = updateTmp
|
up = updateTmp
|
||||||
time.sleep(0.2)
|
time.sleep(0.3)
|
||||||
up = update(gameId)
|
|
||||||
pygame.display.flip()
|
pygame.display.flip()
|
||||||
|
up = update(gameId)
|
||||||
|
|
||||||
pygame.quit()
|
pygame.quit()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|
15
doc/api.org
15
doc/api.org
|
@ -4,14 +4,10 @@
|
||||||
#+LATEX_HEADER: \usepackage{fullpage}
|
#+LATEX_HEADER: \usepackage{fullpage}
|
||||||
|
|
||||||
* General Description
|
* General Description
|
||||||
- All transmissions will be based on TCP since:
|
- All transmissions will be based on UDP since latency is important
|
||||||
- Packet length are not fixed (large variance depending on the snake size and food)
|
- All UDP datagrams between *client and server* will contain _plain json data_
|
||||||
- Packet ordering is important (inverted request can compromise gameplay)
|
- All data should be sent in *one* datagram
|
||||||
- All TCP streams from *client to server* will:
|
- All utf-8 characters in UDP datagram are in lower case
|
||||||
- Contain _plain json data_
|
|
||||||
- Be terminated by an "#EOF" line (in order for the server to detect the end of the client request)
|
|
||||||
- All TCP stream from *server to client* will contains _plain json data_ (connection will be closed by the server
|
|
||||||
so, there is no need of "#EOF").
|
|
||||||
* Communications
|
* Communications
|
||||||
** Initialisation
|
** Initialisation
|
||||||
1. Client sent:
|
1. Client sent:
|
||||||
|
@ -19,7 +15,6 @@
|
||||||
{
|
{
|
||||||
"type": "new-game"
|
"type": "new-game"
|
||||||
}
|
}
|
||||||
#EOF
|
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
2. Server can reply:
|
2. Server can reply:
|
||||||
#+BEGIN_SRC json
|
#+BEGIN_SRC json
|
||||||
|
@ -40,7 +35,6 @@
|
||||||
"game-id": 1,
|
"game-id": 1,
|
||||||
"direction": "left",
|
"direction": "left",
|
||||||
}
|
}
|
||||||
#EOF
|
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
2. Then, server can reply:
|
2. Then, server can reply:
|
||||||
#+BEGIN_SRC json
|
#+BEGIN_SRC json
|
||||||
|
@ -60,7 +54,6 @@
|
||||||
"game-id": 1,
|
"game-id": 1,
|
||||||
"direction": null
|
"direction": null
|
||||||
}
|
}
|
||||||
#EOF
|
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
2. Server can reply:
|
2. Server can reply:
|
||||||
#+BEGIN_SRC json
|
#+BEGIN_SRC json
|
||||||
|
|
BIN
doc/api.pdf
BIN
doc/api.pdf
Binary file not shown.
|
@ -33,6 +33,7 @@
|
||||||
((equal dir "down") (setf (getf p-request :direction) :down))
|
((equal dir "down") (setf (getf p-request :direction) :down))
|
||||||
((equal dir "left") (setf (getf p-request :direction) :left))
|
((equal dir "left") (setf (getf p-request :direction) :left))
|
||||||
((equal dir "right") (setf (getf p-request :direction) :right))))))
|
((equal dir "right") (setf (getf p-request :direction) :right))))))
|
||||||
|
((equal type "admin"))
|
||||||
((not (equal type "new-game"))
|
((not (equal type "new-game"))
|
||||||
(error 'bad-request :msg "Unknown request type")))
|
(error 'bad-request :msg "Unknown request type")))
|
||||||
p-request)))
|
p-request)))
|
||||||
|
@ -43,8 +44,9 @@
|
||||||
(let* ((game-id (create-game gm)))
|
(let* ((game-id (create-game gm)))
|
||||||
(let ((game-dump (dump gm game-id)))
|
(let ((game-dump (dump gm game-id)))
|
||||||
(setf (getf game-dump :game-over) :null) ; Define nil as null (for json)
|
(setf (getf game-dump :game-over) :null) ; Define nil as null (for json)
|
||||||
(to-json
|
(string-downcase
|
||||||
(append (list :type "state") game-dump))))))
|
(to-json
|
||||||
|
(append (list :type "state") game-dump)))))))
|
||||||
|
|
||||||
(defmethod handle-update ((api api) data)
|
(defmethod handle-update ((api api) data)
|
||||||
(with-slots (gm) api
|
(with-slots (gm) api
|
||||||
|
@ -54,9 +56,20 @@
|
||||||
(if dir
|
(if dir
|
||||||
(refresh game :dir dir)
|
(refresh game :dir dir)
|
||||||
(refresh game))
|
(refresh game))
|
||||||
(to-json
|
(string-downcase
|
||||||
(append (list :type "state") (dump gm game-id))))))
|
(to-json
|
||||||
|
(append (list :type "state") (dump gm game-id)))))))
|
||||||
|
|
||||||
|
;;; TODO: debug this function
|
||||||
|
(defmethod handle-admin ((api api) data)
|
||||||
|
(with-slots (gm) api
|
||||||
|
(let* ((game-id (getf data :game-id))
|
||||||
|
(cmd (getf data :cmd))
|
||||||
|
(arg (getf data :arg))
|
||||||
|
(game (get-game gm game-id)))
|
||||||
|
(cond
|
||||||
|
((equal cmd "move") (admin game :move arg)))))
|
||||||
|
"Command executed!")
|
||||||
|
|
||||||
(defmethod handle-request ((api api) request)
|
(defmethod handle-request ((api api) request)
|
||||||
;; Catch request error and send it to the client
|
;; Catch request error and send it to the client
|
||||||
|
@ -66,6 +79,7 @@
|
||||||
(cond
|
(cond
|
||||||
((equal type "new-game") (handle-new-game api data))
|
((equal type "new-game") (handle-new-game api data))
|
||||||
((equal type "update") (handle-update api data))
|
((equal type "update") (handle-update api data))
|
||||||
|
((equal type "admin") (handle-admin api data))
|
||||||
(t (format t "Unknow type"))))
|
(t (format t "Unknow type"))))
|
||||||
(error (condition) ; Send reason to the client
|
(error (condition) ; Send reason to the client
|
||||||
(let ((reason (make-array 0
|
(let ((reason (make-array 0
|
||||||
|
|
|
@ -21,6 +21,12 @@
|
||||||
:initform nil
|
:initform nil
|
||||||
:accessor game-over)))
|
:accessor game-over)))
|
||||||
|
|
||||||
|
;;; Admin function
|
||||||
|
(defmethod admin ((g game) &key (move nil move-supplied-p))
|
||||||
|
(with-slots (snake) g
|
||||||
|
(when move-supplied-p (setf (nth 0 snake) move))))
|
||||||
|
|
||||||
|
|
||||||
;;; Class constructor to initialize the snake
|
;;; Class constructor to initialize the snake
|
||||||
(defmethod initialize-instance :after ((g game) &key)
|
(defmethod initialize-instance :after ((g game) &key)
|
||||||
(with-slots (snake initial-size initial-position) g
|
(with-slots (snake initial-size initial-position) g
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
(:export
|
(:export
|
||||||
#:game
|
#:game
|
||||||
#:dump
|
#:dump
|
||||||
#:refresh))
|
#:refresh
|
||||||
|
#:admin))
|
||||||
|
|
||||||
(defpackage :remote-snake-server-api
|
(defpackage :remote-snake-server-api
|
||||||
(:nicknames :rsapi)
|
(:nicknames :rsapi)
|
||||||
|
@ -16,6 +17,7 @@
|
||||||
|
|
||||||
(defpackage :remote-snake-server
|
(defpackage :remote-snake-server
|
||||||
(:nicknames :rss)
|
(:nicknames :rss)
|
||||||
(:use :common-lisp :usocket :remote-snake-server-api :cl-strings)
|
(:use :common-lisp :usocket :remote-snake-server-api :cl-strings :jonathan)
|
||||||
(:export
|
(:export
|
||||||
#:start))
|
#:start
|
||||||
|
#:send-cmd))
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
:description "Remote Snake Server."
|
:description "Remote Snake Server."
|
||||||
:version "0.0.1"
|
:version "0.0.1"
|
||||||
:author "Loic Guegan"
|
:author "Loic Guegan"
|
||||||
:depends-on ( "usocket" "jonathan" "cl-strings" )
|
:depends-on ( "usocket-server" "jonathan" "cl-strings" )
|
||||||
:components ((:file "packages")
|
:components ((:file "packages")
|
||||||
(:module "game"
|
(:module "game"
|
||||||
:depends-on ("packages")
|
:depends-on ("packages")
|
||||||
|
|
|
@ -6,24 +6,25 @@
|
||||||
(defparameter *server-buffer* (make-array 10000
|
(defparameter *server-buffer* (make-array 10000
|
||||||
:element-type '(unsigned-byte 8)
|
:element-type '(unsigned-byte 8)
|
||||||
:initial-element 0))
|
:initial-element 0))
|
||||||
|
;;; TODO: debug this function
|
||||||
|
(defun send-cmd (host port game-id command arg)
|
||||||
|
(let ((socket (usocket:socket-connect host port :protocol :datagram))
|
||||||
|
(request (list :type "admin" :cmd command :game-id game-id :arg arg))
|
||||||
|
(buffer (make-array 500 :element-type '(unsigned-byte 8) :initial-element 0)))
|
||||||
|
(usocket:socket-send socket (string-downcase (to-json request)) 300)
|
||||||
|
(format t (babel:octets-to-string (usocket:socket-receive socket buffer 300)))
|
||||||
|
(force-output t)
|
||||||
|
(usocket:socket-close socket)))
|
||||||
|
|
||||||
(defun handle-client (client-socket)
|
|
||||||
(unwind-protect ; To be sure to close the client socket
|
|
||||||
(let* ((request (read-line (usocket:socket-stream client-socket))))
|
(defun handle-client (buffer) ; echo
|
||||||
(loop while (eq (search "#EOF" request) nil) do
|
(declare (type (simple-array (unsigned-byte 8) *) buffer)) ; Seems to be to tell lisp which type is buffer
|
||||||
(setf request (concatenate 'string request (read-line (usocket:socket-stream client-socket) nil nil))))
|
(let ((request (babel:octets-to-string buffer)))
|
||||||
(setf request (cl-strings:replace-all request "#EOF" ""))
|
(format t "Receive client request: ~a" request)
|
||||||
(format (usocket:socket-stream client-socket) (handle-request *server-api* request))
|
(babel:string-to-octets
|
||||||
(force-output (usocket:socket-stream client-socket)))
|
(handle-request *server-api* request))))
|
||||||
(usocket:socket-close client-socket)))
|
|
||||||
|
|
||||||
|
|
||||||
;;; The server :D
|
;;; The server :D
|
||||||
(defun start (interface port)
|
(defun start (host port)
|
||||||
(format t "Server started!~%")
|
(usocket:socket-server host port #'handle-client nil :in-new-thread nil :protocol :datagram))
|
||||||
(let ((socket (usocket:socket-listen interface port)))
|
|
||||||
(unwind-protect ; To be sure to close server socket
|
|
||||||
(loop
|
|
||||||
(handle-client (usocket:socket-accept socket :element-type 'character)))
|
|
||||||
(usocket:socket-close socket))))
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue