context: /* This Source Code Form is subject to the terms of the Mozilla Public
   * License, v. 2.0. If a copy of the MPL was not distributed with this
   * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  
  using Newtonsoft.Json;
  using System;
  using System.Collections.Generic;
  using System.Diagnostics;
  using System.IO;
  using System.Linq;
  using System.Net.Sockets;
  using System.Text;
  using System.Threading.Tasks;
  
  namespace TelnetGames
  {
      class Tetris : Game
      {
          private new class PlayerClass : Game.PlayerClass
          {
              public ColorPalette colorPalette;
  
              private static readonly ColorPalette compatibilityColorPalette = new CompatibilityColorPalette();
              private static readonly ColorPalette standardColorPalette = new StandardColorPalette();
  
              public PlayerClass(Game.PlayerClass gamePlayer)
              {
                  playerType = gamePlayer.playerType;
                  vt = gamePlayer.vt;
                  supportAixtermColors = gamePlayer.supportAixtermColors;
                  UpdatePalette();
              }
  
              public void UpdatePalette()
              {
                  if (supportAixtermColors)
                      colorPalette = standardColorPalette;
                  else
                      colorPalette = compatibilityColorPalette;
              }
          }
  
          private abstract class ColorPalette
          {
              public VT100.ColorClass Background;
              public VT100.ColorClass Band;
              public VT100.ColorClass Text;
              public VT100.ColorClass[] Pieces;
          }
  
          private class StandardColorPalette : ColorPalette
          {
              public StandardColorPalette()
              {
                  Background = new VT100.ColorClass { Bright = true, Color = VT100.ColorEnum.Blue };
                  Band = new VT100.ColorClass { Bright = false, Color = VT100.ColorEnum.Yellow };
                  Text = new VT100.ColorClass { Bright = true, Color = VT100.ColorEnum.Yellow };
                  Pieces = new VT100.ColorClass[7]
                  {
                      new VT100.ColorClass { Bright = false, Color = VT100.ColorEnum.Cyan },
                      new VT100.ColorClass { Bright = true, Color = VT100.ColorEnum.Cyan },
                      new VT100.ColorClass { Bright = true, Color = VT100.ColorEnum.Green },
                      new VT100.ColorClass { Bright = true, Color = VT100.ColorEnum.Red },
                      new VT100.ColorClass { Bright = false, Color = VT100.ColorEnum.White },
                      new VT100.ColorClass { Bright = true, Color = VT100.ColorEnum.Magneta },
                      new VT100.ColorClass { Bright = true, Color = VT100.ColorEnum.Yellow }
                  };
              }
          }
  
          private class CompatibilityColorPalette : ColorPalette
          {
              public CompatibilityColorPalette()
              {
                  Background = new VT100.ColorClass { Bright = false, Color = VT100.ColorEnum.Black };
                  Band = new VT100.ColorClass { Bright = false, Color = VT100.ColorEnum.Yellow };
                  Text = new VT100.ColorClass { Bright = false, Color = VT100.ColorEnum.White };
                  Pieces = new VT100.ColorClass[7]
                  {
                      new VT100.ColorClass { Bright = false, Color = VT100.ColorEnum.Cyan },
                      new VT100.ColorClass { Bright = false, Color = VT100.ColorEnum.Cyan },
                      new VT100.ColorClass { Bright = false, Color = VT100.ColorEnum.Cyan },
                      new VT100.ColorClass { Bright = false, Color = VT100.ColorEnum.Cyan },
                      new VT100.ColorClass { Bright = false, Color = VT100.ColorEnum.Cyan },
                      new VT100.ColorClass { Bright = false, Color = VT100.ColorEnum.Cyan },
                      new VT100.ColorClass { Bright = false, Color = VT100.ColorEnum.Cyan }
                  };
              }
          }
  
          private enum GameState
          {
              Stopped,
              Game,
              Highscore
          }
  
          private class PlayerRemovedException : ApplicationException { }
  
          private PlayerClass currentPlayer;
          private List<PlayerClass> players = new List<PlayerClass>();
  
          public override int MinPlayers { get { return 1; } }
          public override int MaxPlayers { get { return 1; } }
          public override int PlayerCount { get { return (currentPlayer != null ? 1 : 0); } }
  
          private GameState gameState = GameState.Stopped;
          private byte[,] board = new byte[20, 10];
  
          private static byte[][][,] blocks = new byte[][][,]
          {
              new byte[][,]
              {
                  new byte[4, 4]
                  {
                      {0, 0, 0, 0},
                      {0, 1, 1, 0},
                      {0, 1, 1, 0},
                      {0, 0, 0, 0}
                  }
              },
              new byte[][,]
              {
                  new byte[4, 4]
                  {
                      {0, 0, 0, 0},
                      {2, 2, 2, 2},
                      {0, 0, 0, 0},
                      {0, 0, 0, 0}
                  },
                  new byte[4, 4]
                  {
                      {0, 0, 2, 0},
                      {0, 0, 2, 0},
                      {0, 0, 2, 0},
                      {0, 0, 2, 0}
                  }
              },
              new byte[][,]
              {
                  new byte[4, 4]
                  {
                      {0, 0, 0, 0},
                      {0, 0, 3, 3},
                      {0, 3, 3, 0},
                      {0, 0, 0, 0}
                  },
                  new byte[4, 4]
                  {
                      {0, 0, 3, 0},
                      {0, 0, 3, 3},
                      {0, 0, 0, 3},
                      {0, 0, 0, 0}
                  }
              },
              new byte[][,]
              {
                  new byte[4, 4]
                  {
                      {0, 0, 0, 0},
                      {0, 4, 4, 0},
                      {0, 0, 4, 4},
                      {0, 0, 0, 0}
                  },
                  new byte[4, 4]
                  {
                      {0, 0, 0, 4},
                      {0, 0, 4, 4},
                      {0, 0, 4, 0},
                      {0, 0, 0, 0}
                  }
              },
              new byte[][,]
              {
                  new byte[4, 4]
                  {
                      {0, 0, 0, 0},
                      {0, 5, 5, 5},
                      {0, 5, 0, 0},
                      {0, 0, 0, 0}
                  },
                  new byte[4, 4]
                  {
                      {0, 0, 5, 0},
                      {0, 0, 5, 0},
                      {0, 0, 5, 5},
                      {0, 0, 0, 0}
                  },
                   new byte[4, 4]
                  {
                      {0, 0, 0, 5},
                      {0, 5, 5, 5},
                      {0, 0, 0, 0},
                      {0, 0, 0, 0}
                  },
                  new byte[4, 4]
                  {
                      {0, 5, 5, 0},
                      {0, 0, 5, 0},
                      {0, 0, 5, 0},
                      {0, 0, 0, 0}
                  }
              },
              new byte[][,]
              {
                  new byte[4, 4]
                  {
                      {0, 0, 0, 0},
                      {0, 6, 6, 6},
                      {0, 0, 0, 6},
                      {0, 0, 0, 0}
                  },
                  new byte[4, 4]
                  {
                      {0, 0, 6, 6},
                      {0, 0, 6, 0},
                      {0, 0, 6, 0},
                      {0, 0, 0, 0}
                  },
                  new byte[4, 4]
                  {
                      {0, 6, 0, 0},
                      {0, 6, 6, 6},
                      {0, 0, 0, 0},
                      {0, 0, 0, 0}
                  },
                  new byte[4, 4]
                  {
                      {0, 0, 6, 0},
                      {0, 0, 6, 0},
                      {0, 6, 6, 0},
                      {0, 0, 0, 0}
                  }
              },
              new byte[][,]
              {
                  new byte[4, 4]
                  {
                      {0, 0, 0, 0},
                      {0, 7, 7, 7},
                      {0, 0, 7, 0},
                      {0, 0, 0, 0}
                  },
                  new byte[4, 4]
                  {
                      {0, 0, 7, 0},
                      {0, 0, 7, 7},
                      {0, 0, 7, 0},
                      {0, 0, 0, 0}
                  },
                   new byte[4, 4]
                  {
                      {0, 0, 7, 0},
                      {0, 7, 7, 7},
                      {0, 0, 0, 0},
                      {0, 0, 0, 0}
                  },
                  new byte[4, 4]
                  {
                      {0, 0, 7, 0},
                      {0, 7, 7, 0},
                      {0, 0, 7, 0},
                      {0, 0, 0, 0}
                  }
              },
          };
  
          private int? block;
          private int blockRotation;
          private int blockX;
          private int blockY;
          private int wait;
          private int lines;
          private int level;
          private int points;
          private int freefall;
          private Random random = new Random();
          private StringBuilder input = new StringBuilder();
          private StringBuilder name = new StringBuilder();
  
          private int LevelCompute(int lines)
          {
              if (lines <= 0)
                  return 1;
              else if ((lines >= 1) && (lines <= 90))
                  return 1 + ((lines - 1) / 10);
              else if (lines >= 91)
                  return 10;
              return 0;
          }
  
          public override void Tick()
          {
              if (gameState == GameState.Stopped)
                  return;
              try
              {
                  HandleInput();
                  if (gameState == GameState.Game)
                  {
                      level = LevelCompute(lines);
                      if (wait == 0)
                      {
                          wait = 10 - level;
                          ComputeLogic();
                      }
                      else
                          wait--;
                      RenderFrame();
                  }
                  else if (gameState == GameState.Highscore)
                  {
                      if (input.Length != 0)
                      {
                          string s = input.ToString();
                          foreach (char c in s)
                          {
                              if (c >= 48 && c <= 122 && name.Length < 10)
                              {
                                  name.Append(c);
                                  currentPlayer.vt.WriteText(c.ToString());
                              }
                              if ((c == 8 || c == 127) && name.Length > 0)
                              {
                                  currentPlayer.vt.WriteText("\x08 \x08");
                                  name.Remove(name.Length - 1, 1);
                              }
                              if (c == 13)
                              {
                                  if (name.Length == 0)
                                      name.Append("ANONYM");
                                  SaveHighscore(new KeyValuePair<string, int[]>(name.ToString(), new int[] { lines, points }));
                                  gameState = GameState.Stopped;
                                  KillGame();
                              }
                          }
                      }
                      currentPlayer.vt.WriteText("\x00");
                  }
                  Flush();
              }
              catch (PlayerRemovedException) { }
          }
          
          private void SaveHighscore(KeyValuePair<string, int[]> highscore)
          {
              FileStream fileR = new FileStream("tetris-highscores", FileMode.Open);
              StreamReader reader = new StreamReader(fileR);
              List<KeyValuePair<string, int[]>> table = JsonConvert.DeserializeObject<List<KeyValuePair<string, int[]>>>(reader.ReadToEnd());
              reader.Close();
              table.Add(highscore);
  			table.Sort((y, x) => x.Value[1].CompareTo(y.Value[1]));
              FileStream fileW = new FileStream("tetris-highscores", FileMode.Create);
              StreamWriter writer = new StreamWriter(fileW);
              writer.Write(JsonConvert.SerializeObject(table));
              writer.Close();
          }
  
          public override void AddPlayer(Game.PlayerClass player)
          {
              currentPlayer = new PlayerClass(player);
              players.Add(currentPlayer);
              currentPlayer.vt.Bell();
              currentPlayer.vt.SetCursorVisiblity(false);
              Flush();
              gameState = GameState.Game;
          }
  
          public override void KillGame()
          {
              if (gameState == GameState.Game)
                  SaveHighscore(new KeyValuePair<string, int[]>("ANONYM", new int[] { lines, points }));
              for (int i = players.Count - 1; i >= 0; i--)
              {
                  RemovePlayer(players[i]);
              }
              GameKilledRaise();
              throw new PlayerRemovedException();
          }
  
          private void RemovePlayer(PlayerClass player)
          {
              players.Remove(player);
              if (player.playerType == PlayerType.Player)
              {
                  currentPlayer = null;
                  gameState = GameState.Stopped;
              }
              if (player.vt.Flush() == VT100.FlushReturnState.Success)
                  PlayerLeftRaise(player, false);
              else
                  PlayerLeftRaise(player, true);
              if (player.playerType == PlayerType.Player)
                  if (currentPlayer == null)
                      KillGame();
          }
  
          private void ComputeLogic()
          {
              if (block == null || freefall++ < 0 || !LineDown())
              {
                  block = random.Next(blocks.Length);
                  blockRotation = 0;
                  blockX = 5;
                  blockY = 0;
                  freefall = 0;
                  if (CheckCollision())
                  {
                      RenderHighscore();
                      //KillGame();
                      Flush();
                      throw new PlayerRemovedException();
                  }
              }
          }
  
          private void RenderHighscore()
          {
              FileStream file = new FileStream("tetris-highscores", FileMode.Open);
              StreamReader reader = new StreamReader(file);
              List<KeyValuePair<string, int[]>> table = JsonConvert.DeserializeObject<List<KeyValuePair<string, int[]>>>(reader.ReadToEnd());
              reader.Close();
              currentPlayer.vt.SetBackgroundColor(currentPlayer.colorPalette.Background);
              currentPlayer.vt.SetForegroundColor(currentPlayer.colorPalette.Text);
              currentPlayer.vt.ClearScreen();
              currentPlayer.vt.SetCursor(30, 4);
              currentPlayer.vt.WriteText("TETRIS HIGHSCORES");
              currentPlayer.vt.SetCursor(15, 6);
              currentPlayer.vt.WriteText("NICKNAME            LEVEL     LINES     SCORE");
              for (int i = 0; i < table.Count; i++)
              {
                  if (i >= 10)
                      break;
                  currentPlayer.vt.SetCursor(15, 7 + i);
                  currentPlayer.vt.WriteText(table[i].Key);
                  currentPlayer.vt.SetCursor(35, 7 + i);
                  currentPlayer.vt.WriteText(LevelCompute(table[i].Value[0]).ToString());
                  currentPlayer.vt.SetCursor(45, 7 + i);
                  currentPlayer.vt.WriteText(table[i].Value[0].ToString());
                  currentPlayer.vt.SetCursor(55, 7 + i);
                  currentPlayer.vt.WriteText(table[i].Value[1].ToString());
              }
              currentPlayer.vt.SetCursor(15, 19);
              currentPlayer.vt.WriteText("Enter nickname (leave empty for ANONYM):");
              currentPlayer.vt.SetCursor(35, 20);
              currentPlayer.vt.WriteText(level.ToString());
              currentPlayer.vt.SetCursor(45, 20);
              currentPlayer.vt.WriteText(lines.ToString());
              currentPlayer.vt.SetCursor(55, 20);
              currentPlayer.vt.WriteText(points.ToString());
              currentPlayer.vt.SetCursor(15, 20);
              currentPlayer.vt.SetCursorVisiblity(true);
              gameState = GameState.Highscore;
          }
  
          private bool LineDown()
          {
              blockY++;
              if (CheckCollision())
              {
                  blockY--;
                  HardenizeBlock();
                  return false;
              }
              return true;
          }
  
          private void HardenizeBlock()
          {
              for (int x = 0; x < 4; x++)
              {
                  for (int y = 0; y < 4; y++)
                  {
                      if (blocks[(int)block][blockRotation][y, x] != 0)
                      {
                          int mX = blockX + x - 2;
                          int mY = blockY + y - 1;
                          board[mY, mX] = blocks[(int)block][blockRotation][y, x];
                      }
                  }
              }
              points += ((21 + (3 * level)) - freefall);
              block = null;
              for (int y = 19; y >= 0; y--)
              {
                  for (int x = 9; x >= 0; x--)
                  {
  
gt:                     if (board[y, x] == 0)