// Spludlow Software // Copyright © Samuel P. Ludlow 2020 All Rights Reserved // Distributed under the terms of the GNU General Public License version 3 // Distributed WITHOUT ANY WARRANTY; without implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE // https://www.spludlow.co.uk/LICENCE.TXT // The Spludlow logo is a registered trademark of Samuel P. Ludlow and may not be used without permission // v1.14 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Net; using System.Net.Sockets; using System.Threading; using System.Diagnostics; namespace Spludlow.Tetris { /// /// Client class used by the UI or Bot that handles server communtication /// The UI should handle the 4 events and can call the Move methods /// The Boards are cached here. Normally a UI can just handle the DrawBoardEvent but the Boards can be accessed directly like with the Bot /// public class TetrisClient { private System.Threading.EventWaitHandle _WaitFinished = null; private Socket _ServerSocket = null; public object InfoLock = new object(); public TetrisClientInfo Info = null; public object BoardLock = new object(); public Dictionary Boards = new Dictionary(); public Dictionary PreviousBoards = new Dictionary(); public delegate void DrawBoard(int boardKey, TetrisBoard board, TetrisBoard previousBoard, bool reDraw); public event DrawBoard DrawBoardEvent; public delegate void ReceiveText(string text); public event ReceiveText ReceiveTextEvent; public delegate void ReceiveInfo(TetrisClientInfo info); public event ReceiveInfo ReceiveInfoEvent; public delegate void ReceiveSound(string sound); public event ReceiveSound ReceiveSoundEvent; public class ClientState { public ClientState() { this.ClientDisplayName = Spludlow.RandomKeys.Letters(6); this.HostOrAddressAndPort = "127.0.0.1"; this.NoServerSounds = !OnlyProcess(); this.Volume = 2; //10; this.InputIndex = 0; this.ServerTick = 1000; this.ServerWidth = 10; this.ServerHeight = 22; } public string ClientDisplayName { get; set; } public string HostOrAddressAndPort { get; set; } public bool NoServerSounds { get; set; } public int Volume { get; set; } public int InputIndex { get; set; } public int ServerTick { get; set; } public int ServerWidth { get; set; } public int ServerHeight { get; set; } public bool ClientRunning { get; set; } public bool ServerRuning { get; set; } public string Command { get; set; } } public static bool OnlyProcess() { return (Process.GetProcessesByName(Process.GetCurrentProcess().ProcessName).Length == 1); } public void Run(string hostNameOrAddressAndPortNumber, string displayName) { _WaitFinished = new EventWaitHandle(false, EventResetMode.ManualReset); IPEndPoint serverEndPoint = TetrisServer.CreateEndpoint(hostNameOrAddressAndPortNumber); using (_ServerSocket = new Socket(serverEndPoint.Address.AddressFamily, SocketType.Stream, ProtocolType.Tcp)) { _ServerSocket.Connect(serverEndPoint); Spludlow.Log.Info("Tetris Client, Connected to server"); TetrisMessage message; // Send Display Name message = new TetrisMessage(TetrisMessage.BodyTypes.Name, TetrisServer.Encoding.GetBytes(displayName)); message.SocketSend(_ServerSocket); while (true) { try { message = TetrisMessage.Receive(_ServerSocket, _WaitFinished); if (message == null) break; } catch (SocketException ee) { if (ee.ErrorCode == 10053 || ee.ErrorCode == 10054 || ee.ErrorCode == 10060) { Spludlow.Log.Info("Tetris Client, Closed from Server" + ee.ErrorCode, ee); break; } Spludlow.Log.Error("Tetris Client, Receive SocketException: " + ee.ErrorCode, ee); throw ee; } switch ((TetrisMessage.BodyTypes)message.BodyType) { case TetrisMessage.BodyTypes.Text: string text = TetrisServer.Encoding.GetString(message.Body); if (this.ReceiveTextEvent != null) this.ReceiveTextEvent(text); break; case TetrisMessage.BodyTypes.Board: TetrisBoard board = new TetrisBoard(message.Body); TetrisBoard previousBoard = null; if (board.BoardKey != Int32.MaxValue) { lock (this.BoardLock) { if (this.Boards.ContainsKey(board.BoardKey) == true) { if (this.PreviousBoards.ContainsKey(board.BoardKey) == true) this.PreviousBoards[board.BoardKey] = this.Boards[board.BoardKey]; else this.PreviousBoards.Add(board.BoardKey, this.Boards[board.BoardKey]); } if (this.Boards.ContainsKey(board.BoardKey) == true) this.Boards[board.BoardKey] = board; else this.Boards.Add(board.BoardKey, board); if (this.PreviousBoards.ContainsKey(board.BoardKey) == true) previousBoard = this.PreviousBoards[board.BoardKey]; } } if (this.DrawBoardEvent != null) this.DrawBoardEvent(board.BoardKey, board, previousBoard, board.ReDraw); break; case TetrisMessage.BodyTypes.Sound: string sound = TetrisServer.Encoding.GetString(message.Body); if (this.ReceiveSoundEvent != null) this.ReceiveSoundEvent(sound); break; case TetrisMessage.BodyTypes.Info: lock (this.InfoLock) // ?? need { this.Info = new TetrisClientInfo(message.Body); if (this.ReceiveInfoEvent != null) this.ReceiveInfoEvent(this.Info); } break; } } } Spludlow.Log.Info("Tetris Client, Disconnected"); } public void Stop() { if (_WaitFinished != null) _WaitFinished.Set(); Spludlow.Log.Info("Tetris Client, Asked to Stop"); } public void ReDrawBoards() { lock (this.BoardLock) { foreach (int boardKey in this.Boards.Keys) { if (boardKey <= 0 || boardKey == this.Info.TargetClientId) { TetrisBoard[] boards = GetBoards(boardKey); this.DrawBoardEvent(boardKey, boards[0], boards[1], true); } } } } public TetrisBoard[] GetBoards(int boardKey) { TetrisBoard board = null; if (this.Boards.ContainsKey(boardKey) == true) board = this.Boards[boardKey]; TetrisBoard previousBoard = null; if (this.PreviousBoards.ContainsKey(boardKey) == true) previousBoard = this.PreviousBoards[boardKey]; return new TetrisBoard[] { board, previousBoard }; } public void Up() { Move(0); } public void Down() { Move(1); } public void Left() { Move(2); } public void Right() { Move(3); } public void RotateCW() { Move(4); } public void RotateCCW() { Move(5); } public void TargetNext() { Move(6); } public void Move(byte command) { byte[] data = new byte[] { command }; TetrisMessage message = new TetrisMessage(TetrisMessage.BodyTypes.Move, data); message.SocketSend(_ServerSocket); } public void SendText(string text) { TetrisMessage message = new TetrisMessage(TetrisMessage.BodyTypes.Text, TetrisServer.Encoding.GetBytes(text)); message.SocketSend(_ServerSocket); } } }