From ae8fa0d21d6e7c7effd72508a40356d688f680c5 Mon Sep 17 00:00:00 2001 From: Colin McMillen Date: Fri, 17 Jan 2020 22:41:45 -0500 Subject: [PATCH] Revert "Add .gitignore and .gitattributes." This reverts commit 5c9f574644ecd78b112ea857d658f670ef4773e3. GitOrigin-RevId: 277054282d105e4a5f185ac51983581c89b8a031 --- README.md | 2 + Shared/Camera.cs | 25 ++++ Shared/Debug.cs | 78 +++++++++++ Shared/FpsCounter.cs | 23 ++++ Shared/History.cs | 62 +++++++++ Shared/IDisplay.cs | 8 ++ Shared/Input.cs | 53 ++++++++ Shared/LICENSE.txt | 21 +++ Shared/Player.cs | 175 ++++++++++++++++++++++++ Shared/Shared.projitems | 22 +++ Shared/Shared.shproj | 26 ++++ Shared/SneakGame.cs | 145 ++++++++++++++++++++ Shared/World.cs | 180 +++++++++++++++++++++++++ SharedTests/HistoryTests.cs | 48 +++++++ SharedTests/Properties/AssemblyInfo.cs | 20 +++ SharedTests/SharedTests.csproj | 72 ++++++++++ SharedTests/packages.config | 5 + tools/LICENSE.txt | 21 +++ tools/copybara/copy.bara.sky | 25 ++++ tools/copybara/copybara.sh | 6 + 20 files changed, 1017 insertions(+) create mode 100644 README.md create mode 100644 Shared/Camera.cs create mode 100644 Shared/Debug.cs create mode 100644 Shared/FpsCounter.cs create mode 100644 Shared/History.cs create mode 100644 Shared/IDisplay.cs create mode 100644 Shared/Input.cs create mode 100644 Shared/LICENSE.txt create mode 100644 Shared/Player.cs create mode 100644 Shared/Shared.projitems create mode 100644 Shared/Shared.shproj create mode 100644 Shared/SneakGame.cs create mode 100644 Shared/World.cs create mode 100644 SharedTests/HistoryTests.cs create mode 100644 SharedTests/Properties/AssemblyInfo.cs create mode 100644 SharedTests/SharedTests.csproj create mode 100644 SharedTests/packages.config create mode 100644 tools/LICENSE.txt create mode 100644 tools/copybara/copy.bara.sky create mode 100644 tools/copybara/copybara.sh diff --git a/README.md b/README.md new file mode 100644 index 0000000..53576af --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# sneak +mcmillen's first indie game diff --git a/Shared/Camera.cs b/Shared/Camera.cs new file mode 100644 index 0000000..65fdbe6 --- /dev/null +++ b/Shared/Camera.cs @@ -0,0 +1,25 @@ +using Microsoft.Xna.Framework; +using System; + +// Good background reading, eventually: +// https://gamasutra.com/blogs/ItayKeren/20150511/243083/Scroll_Back_The_Theory_and_Practice_of_Cameras_in_SideScrollers.php +namespace SemiColinGames { + class Camera { + private Rectangle bbox = new Rectangle(0, 0, 1920 / 4, 1080 / 4); + + public int Width { get => bbox.Width; } + public int Height { get => bbox.Height; } + public int Left { get => bbox.Left; } + + public void Update(GameTime time, Point player) { + int diff = player.X - bbox.Center.X; + if (Math.Abs(diff) > 16) { + bbox.Offset((int) (diff * 0.1), 0); + } + if (bbox.Left < 0) { + bbox.Offset(-bbox.Left, 0); + } + // Debug.Toast($"p: {player.X}, {player.Y} c: {bbox.Center.X}"); + } + } +} diff --git a/Shared/Debug.cs b/Shared/Debug.cs new file mode 100644 index 0000000..b89c672 --- /dev/null +++ b/Shared/Debug.cs @@ -0,0 +1,78 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System.Collections.Generic; + +namespace SemiColinGames { + static class Debug { + struct DebugRect { + public Rectangle rect; + public Color color; + + public DebugRect(Rectangle rect, Color color) { + this.rect = rect; + this.color = color; + } + } + + public static bool Enabled; + static List rects = new List(); + static Texture2D whiteTexture; + static string toast = null; + + public static void Toast(string s) { + toast = s; + } + + public static void WriteLine(string s) { + System.Diagnostics.Debug.WriteLine(s); + } + + public static void WriteLine(string s, params object[] args) { + System.Diagnostics.Debug.WriteLine(s, args); + } + + public static void Initialize(GraphicsDevice graphics) { + whiteTexture = new Texture2D(graphics, 1, 1); + whiteTexture.SetData(new Color[] { Color.White }); + } + + public static void Clear() { + rects.Clear(); + } + + public static void AddRect(Rectangle rect, Color color) { + rects.Add(new DebugRect(rect, color)); + } + + public static void DrawToast(SpriteBatch spriteBatch, SpriteFont font) { + if (toast == null) { + return; + } + spriteBatch.DrawString(font, toast, new Vector2(10, 40), Color.Teal); + toast = null; + } + + public static void Draw(SpriteBatch spriteBatch, Camera camera) { + if (!Enabled) { + return; + } + foreach (var debugRect in rects) { + var rect = debugRect.rect; + rect.Offset(-camera.Left, 0); + var color = debugRect.color; + // top side + spriteBatch.Draw( + whiteTexture, new Rectangle(rect.Left, rect.Top, rect.Width, 1), color); + // bottom side + spriteBatch.Draw( + whiteTexture, new Rectangle(rect.Left, rect.Bottom - 1, rect.Width, 1), color); + // left side + spriteBatch.Draw( + whiteTexture, new Rectangle(rect.Left, rect.Top, 1, rect.Height), color); + // right side + spriteBatch.Draw( + whiteTexture, new Rectangle(rect.Right - 1, rect.Top, 1, rect.Height), color); + } + } + } +} diff --git a/Shared/FpsCounter.cs b/Shared/FpsCounter.cs new file mode 100644 index 0000000..fff76f7 --- /dev/null +++ b/Shared/FpsCounter.cs @@ -0,0 +1,23 @@ +using System; + +namespace SemiColinGames { + class FpsCounter { + private double fps = 0; + private int[] frameTimes = new int[60]; + private int idx = 0; + + public int Fps { + get => (int) Math.Ceiling(fps); + } + + public void Update() { + var now = Environment.TickCount; // ms + if (frameTimes[idx] != 0) { + var timeElapsed = now - frameTimes[idx]; + fps = 1000.0 * frameTimes.Length / timeElapsed; + } + frameTimes[idx] = now; + idx = (idx + 1) % frameTimes.Length; + } + } +} diff --git a/Shared/History.cs b/Shared/History.cs new file mode 100644 index 0000000..56abd77 --- /dev/null +++ b/Shared/History.cs @@ -0,0 +1,62 @@ +using System; + +namespace SemiColinGames { + // A History is a queue of fixed length N that records the N most recent items Add()ed to it. + // The mostly-recently-added item is found at index 0; the least-recently-added item is at index + // N-1. Items older than the History's size are automatically dropped. The underlying + // implementation is a fixed-size circular array; insertion and access are O(1). + // + // Example: + // h = new History(3); + // h.Add(2); h.Add(3); h.Add(5); + // Console.WriteLine("{0} {1} {2}", h[0], h[1], h[2]); // 5 3 2 + // h.Add(7); + // Console.WriteLine("{0} {1} {2}", h[0], h[1], h[2]); // 7 5 3 + // h.Add(11); h.Add(13); + // Console.WriteLine("{0} {1} {2}", h[0], h[1], h[2]); // 13 11 7 + class History { + + // Backing store for the History's items. + private readonly T[] items; + // Points at the most-recently-inserted item. + private int idx = 0; + + public History(int length) { + items = new T[length]; + } + + public void Add(T item) { + idx++; + if (idx >= items.Length) { + idx -= items.Length; + } + items[idx] = item; + } + + public int Length { + get => items.Length; + } + + public T this[int age] { + get { + if (age < 0 || age >= items.Length) { + throw new IndexOutOfRangeException(); + } + int lookup = idx - age; + if (lookup < 0) { + lookup += items.Length; + } + return items[lookup]; + } + } + + // This creates and populates a new array. It's O(n) and should probably only be used for tests. + public T[] ToArray() { + T[] result = new T[items.Length]; + for (int i = 0; i < items.Length; i++) { + result[i] = this[i]; + } + return result; + } + } +} diff --git a/Shared/IDisplay.cs b/Shared/IDisplay.cs new file mode 100644 index 0000000..158915c --- /dev/null +++ b/Shared/IDisplay.cs @@ -0,0 +1,8 @@ +using Microsoft.Xna.Framework; + +namespace SemiColinGames { + public interface IDisplay { + void Initialize(GameWindow window, GraphicsDeviceManager graphics); + void SetFullScreen(bool fullScreen); + } +} diff --git a/Shared/Input.cs b/Shared/Input.cs new file mode 100644 index 0000000..852368e --- /dev/null +++ b/Shared/Input.cs @@ -0,0 +1,53 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; + +namespace SemiColinGames { + struct Input { + public bool Jump; + public bool Attack; + public Vector2 Motion; + + public bool Exit; + public bool FullScreen; + public bool Debug; + + public Input(GamePadState gamePad, KeyboardState keyboard) { + // First we process normal buttons. + Jump = gamePad.IsButtonDown(Buttons.A) || gamePad.IsButtonDown(Buttons.B) || + keyboard.IsKeyDown(Keys.J); + Attack = gamePad.IsButtonDown(Buttons.X) || gamePad.IsButtonDown(Buttons.Y) || + keyboard.IsKeyDown(Keys.K); + + // Then special debugging sorts of buttons. + Exit = gamePad.IsButtonDown(Buttons.Start) || keyboard.IsKeyDown(Keys.Escape); + FullScreen = gamePad.IsButtonDown(Buttons.Back) || keyboard.IsKeyDown(Keys.F12) || + keyboard.IsKeyDown(Keys.OemPlus); + Debug = gamePad.IsButtonDown(Buttons.LeftShoulder) || keyboard.IsKeyDown(Keys.OemMinus); + + // Then potential motion directions. If the player attempts to input opposite directions at + // once (up & down or left & right), those inputs cancel out, resulting in no motion. + Motion = new Vector2(); + Vector2 leftStick = gamePad.ThumbSticks.Left; + bool left = leftStick.X < -0.5 || gamePad.IsButtonDown(Buttons.DPadLeft) || + keyboard.IsKeyDown(Keys.A); + bool right = leftStick.X > 0.5 || gamePad.IsButtonDown(Buttons.DPadRight) || + keyboard.IsKeyDown(Keys.D); + bool up = leftStick.Y > 0.5 || gamePad.IsButtonDown(Buttons.DPadUp) || + keyboard.IsKeyDown(Keys.W); + bool down = leftStick.Y < -0.5 || gamePad.IsButtonDown(Buttons.DPadDown) || + keyboard.IsKeyDown(Keys.S); + if (left && !right) { + Motion.X = -1; + } + if (right && !left) { + Motion.X = 1; + } + if (up && !down) { + Motion.Y = 1; + } + if (down && !up) { + Motion.Y = -1; + } + } + } +} diff --git a/Shared/LICENSE.txt b/Shared/LICENSE.txt new file mode 100644 index 0000000..74ce39c --- /dev/null +++ b/Shared/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Colin McMillen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Shared/Player.cs b/Shared/Player.cs new file mode 100644 index 0000000..76826d1 --- /dev/null +++ b/Shared/Player.cs @@ -0,0 +1,175 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System.Collections.Generic; + +namespace SemiColinGames { + class Player { + enum Facing { Left, Right }; + enum Pose { Walking, Standing, Crouching, Stretching, SwordSwing, Jumping }; + + private const int moveSpeed = 180; + private const int jumpSpeed = -600; + private const int gravity = 2400; + + private Texture2D texture; + private const int spriteSize = 48; + private const int spriteWidth = 7; + + private Point position = new Point(64, 16); + private int jumps = 0; + private Facing facing = Facing.Right; + private Pose pose = Pose.Jumping; + private double swordSwingTime = 0; + private double jumpTime = 0; + private float ySpeed = 0; + + public Player(Texture2D texture) { + this.texture = texture; + } + + public Point Position { get { return position; } } + + private Rectangle Bbox(Point position) { + return new Rectangle(position.X - spriteWidth, position.Y - 7, spriteWidth * 2, 26); + } + + public void Update(GameTime time, History input, List collisionTargets) { + Point oldPosition = position; + Vector2 movement = HandleInput(time, input); + position = new Point((int) (oldPosition.X + movement.X), (int) (oldPosition.Y + movement.Y)); + + Rectangle oldBbox = Bbox(oldPosition); + Rectangle playerBbox = Bbox(position); + bool standingOnGround = false; + + foreach (var rect in collisionTargets) { + playerBbox = Bbox(position); + + // first we check for left-right collisions... + if (playerBbox.Intersects(rect)) { + if (oldBbox.Right <= rect.Left && playerBbox.Right > rect.Left) { + position.X = rect.Left - spriteWidth; + } + if (oldBbox.Left >= rect.Right && playerBbox.Left < rect.Right) { + position.X = rect.Right + spriteWidth; + } + playerBbox = Bbox(position); + } + // after fixing that, we check for hitting our head or hitting the ground. + if (playerBbox.Intersects(rect)) { + if (oldPosition.Y > position.Y) { + int diff = playerBbox.Top - rect.Bottom; + position.Y -= diff; + // TODO: set ySpeed = 0 here so that bonking our head actually reduces hangtime? + } else { + standingOnGround = true; + int diff = playerBbox.Bottom - rect.Top; + position.Y -= diff; + } + } else { + playerBbox.Height += 1; + if (playerBbox.Intersects(rect)) { + standingOnGround = true; + Debug.AddRect(rect, Color.Cyan); + } else { + Debug.AddRect(rect, Color.Green); + } + } + } + if (standingOnGround) { + jumps = 1; + ySpeed = 0; + Debug.AddRect(playerBbox, Color.Red); + } else { + jumps = 0; + Debug.AddRect(playerBbox, Color.Orange); + } + + if (movement.X > 0) { + facing = Facing.Right; + } else if (movement.X < 0) { + facing = Facing.Left; + } + if (swordSwingTime > 0) { + pose = Pose.SwordSwing; + } else if (jumps == 0) { + pose = Pose.Jumping; + } else if (movement.X != 0) { + pose = Pose.Walking; + } else if (input[0].Motion.Y > 0) { + pose = Pose.Stretching; + } else if (input[0].Motion.Y < 0) { + pose = Pose.Crouching; + } else { + pose = Pose.Standing; + } + } + + // Returns the desired (dx, dy) for the player to move this frame. + Vector2 HandleInput(GameTime time, History input) { + Vector2 result = new Vector2(); + result.X = (int) (input[0].Motion.X * moveSpeed * time.ElapsedGameTime.TotalSeconds); + + if (input[0].Jump && !input[1].Jump && jumps > 0) { + jumpTime = 0.3; + jumps--; + ySpeed = jumpSpeed; + } + + if (input[0].Attack && !input[1].Attack && swordSwingTime <= 0) { + swordSwingTime = 0.3; + } + + result.Y = ySpeed * (float) time.ElapsedGameTime.TotalSeconds; + ySpeed += gravity * (float) time.ElapsedGameTime.TotalSeconds; + jumpTime -= time.ElapsedGameTime.TotalSeconds; + swordSwingTime -= time.ElapsedGameTime.TotalSeconds; + return result; + } + + private int SpriteIndex(Pose pose, GameTime time) { + int frameNum = (time.TotalGameTime.Milliseconds / 125) % 4; + if (frameNum == 3) { + frameNum = 1; + } + switch (pose) { + case Pose.Walking: + return 6 + frameNum; + case Pose.Stretching: + return 18 + frameNum; + case Pose.Jumping: + if (jumpTime > 0.2) { + return 15; + } else if (jumpTime > 0.1) { + return 16; + } else { + return 17; + } + case Pose.SwordSwing: + if (swordSwingTime > 0.2) { + return 30; + } else if (swordSwingTime > 0.1) { + return 31; + } else { + return 32; + } + case Pose.Crouching: + return 25; + case Pose.Standing: + default: + return 7; + } + } + + public void Draw(SpriteBatch spriteBatch, Camera camera, GameTime time) { + int index = SpriteIndex(pose, time); + Rectangle textureSource = new Rectangle(index * spriteSize, 0, spriteSize, spriteSize); + Vector2 spriteCenter = new Vector2(spriteSize / 2, spriteSize / 2); + SpriteEffects effect = facing == Facing.Right ? + SpriteEffects.FlipHorizontally : SpriteEffects.None; + Vector2 drawPos = new Vector2(position.X - camera.Left, position.Y); + spriteBatch.Draw(texture, drawPos, textureSource, Color.White, 0f, spriteCenter, + Vector2.One, effect, 0f); + } + } +} diff --git a/Shared/Shared.projitems b/Shared/Shared.projitems new file mode 100644 index 0000000..12c1794 --- /dev/null +++ b/Shared/Shared.projitems @@ -0,0 +1,22 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + 2785994a-a14f-424e-8e77-2e464d28747f + + + SemiColinGames + + + + + + + + + + + + + \ No newline at end of file diff --git a/Shared/Shared.shproj b/Shared/Shared.shproj new file mode 100644 index 0000000..33096c7 --- /dev/null +++ b/Shared/Shared.shproj @@ -0,0 +1,26 @@ + + + + 2785994a-a14f-424e-8e77-2e464d28747f + 14.0 + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Shared/SneakGame.cs b/Shared/SneakGame.cs new file mode 100644 index 0000000..3ba42cd --- /dev/null +++ b/Shared/SneakGame.cs @@ -0,0 +1,145 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; + +using System; +using System.Collections.Generic; + +namespace SemiColinGames { + public class SneakGame : Game { + GraphicsDeviceManager graphics; + RenderTarget2D renderTarget; + + SpriteBatch spriteBatch; + SpriteFont font; + bool fullScreen = false; + IDisplay display; + + History input = new History(2); + + FpsCounter fpsCounter = new FpsCounter(); + Texture2D grasslandBg1; + Texture2D grasslandBg2; + + Player player; + World world; + Camera camera = new Camera(); + + public SneakGame() { + graphics = new GraphicsDeviceManager(this); + IsMouseVisible = true; + Content.RootDirectory = "Content"; + } + + // Performs initialization that's needed before starting to run. + protected override void Initialize() { + display = (IDisplay) Services.GetService(typeof(IDisplay)); + display.Initialize(Window, graphics); + display.SetFullScreen(fullScreen); + + Debug.Initialize(GraphicsDevice); + + renderTarget = new RenderTarget2D( + GraphicsDevice, camera.Width, camera.Height, false /* mipmap */, + GraphicsDevice.PresentationParameters.BackBufferFormat, DepthFormat.Depth24); + + base.Initialize(); + } + + // Called once per game. Loads all game content. + protected override void LoadContent() { + spriteBatch = new SpriteBatch(GraphicsDevice); + font = Content.Load("font"); + + player = new Player(Content.Load("player_1x")); + world = new World(Content.Load("grassland")); + grasslandBg1 = Content.Load("grassland_bg1"); + grasslandBg2 = Content.Load("grassland_bg2"); + } + + // Called once per game. Unloads all game content. + protected override void UnloadContent() { + } + + // Updates the game world. + protected override void Update(GameTime gameTime) { + Debug.Clear(); + + input.Add(new Input(GamePad.GetState(PlayerIndex.One), Keyboard.GetState())); + + if (input[0].Exit) { + Exit(); + } + + if (input[0].FullScreen && !input[1].FullScreen) { + fullScreen = !fullScreen; + display.SetFullScreen(fullScreen); + } + + if (input[0].Debug && !input[1].Debug) { + Debug.Enabled = !Debug.Enabled; + } + + List collisionTargets = world.CollisionTargets(); + player.Update(gameTime, input, collisionTargets); + + camera.Update(gameTime, player.Position); + + base.Update(gameTime); + } + + // Called when the game should draw itself. + protected override void Draw(GameTime gameTime) { + // We need to update the FPS counter in Draw() since Update() might get called more + // frequently, especially when gameTime.IsRunningSlowly. + fpsCounter.Update(); + + // Draw scene to RenderTarget. + GraphicsDevice.SetRenderTarget(renderTarget); + GraphicsDevice.Clear(Color.CornflowerBlue); + spriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.LinearWrap, null, null); + + // Draw background. + Rectangle bgSource = new Rectangle( + (int) (camera.Left * 0.25), 0, camera.Width, camera.Height); + Rectangle bgTarget = new Rectangle(0, 0, camera.Width, camera.Height); + spriteBatch.Draw(grasslandBg2, bgTarget, bgSource, Color.White); + bgSource = new Rectangle( + (int) (camera.Left * 0.5), 0, camera.Width, camera.Height); + spriteBatch.Draw(grasslandBg1, bgTarget, bgSource, Color.White); + + // Draw player. + player.Draw(spriteBatch, camera, gameTime); + + // Draw foreground tiles. + world.Draw(spriteBatch, camera); + + // Draw debug rects. + Debug.Draw(spriteBatch, camera); + + // Aaaaand we're done. + spriteBatch.End(); + + // Draw RenderTarget to screen. + GraphicsDevice.SetRenderTarget(null); + GraphicsDevice.Clear(Color.Black); + spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, + SamplerState.PointClamp, DepthStencilState.Default, + RasterizerState.CullNone); + Rectangle drawRect = new Rectangle( + 0, 0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height); + spriteBatch.Draw(renderTarget, drawRect, Color.White); + + if (Debug.Enabled) { + string fpsText = $"{GraphicsDevice.Viewport.Width}x{GraphicsDevice.Viewport.Height}, " + + $"{fpsCounter.Fps} FPS"; + spriteBatch.DrawString(font, fpsText, new Vector2(10, 10), Color.Teal); + Debug.DrawToast(spriteBatch, font); + } + + spriteBatch.End(); + + base.Draw(gameTime); + } + } +} diff --git a/Shared/World.cs b/Shared/World.cs new file mode 100644 index 0000000..49366b9 --- /dev/null +++ b/Shared/World.cs @@ -0,0 +1,180 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System.Collections.Generic; +using System.Linq; + +namespace SemiColinGames { + enum Terrain { + Empty, + Grass, + GrassL, + GrassR, + Rock, + RockL, + RockR, + Water, + Block + } + + class Tile { + Texture2D texture; + Terrain terrain; + Rectangle position; + + public Tile(Texture2D texture, Terrain terrain, Rectangle position) { + this.texture = texture; + this.terrain = terrain; + this.position = position; + } + + public Rectangle Position { get { return position; } } + public Terrain Terrain { get { return terrain; } } + + public void Draw(SpriteBatch spriteBatch, Camera camera) { + int size = World.TileSize; + Vector2 drawPos = new Vector2(position.Left - camera.Left, position.Top); + switch (terrain) { + case Terrain.Grass: { + Rectangle source = new Rectangle(3 * size, 0 * size, size, size); + spriteBatch.Draw(texture, drawPos, source, Color.White); + break; + } + case Terrain.GrassL: { + Rectangle source = new Rectangle(2 * size, 0 * size, size, size); + spriteBatch.Draw(texture, drawPos, source, Color.White); + break; + } + case Terrain.GrassR: { + Rectangle source = new Rectangle(4 * size, 0 * size, size, size); + spriteBatch.Draw(texture, drawPos, source, Color.White); + break; + } + case Terrain.Rock: { + Rectangle source = new Rectangle(3 * size, 1 * size, size, size); + spriteBatch.Draw(texture, drawPos, source, Color.White); + break; + } + case Terrain.RockL: { + Rectangle source = new Rectangle(1 * size, 2 * size, size, size); + spriteBatch.Draw(texture, drawPos, source, Color.White); + break; + } + case Terrain.RockR: { + Rectangle source = new Rectangle(5 * size, 2 * size, size, size); + spriteBatch.Draw(texture, drawPos, source, Color.White); + break; + } + case Terrain.Water: { + Rectangle source = new Rectangle(9 * size, 2 * size, size, size); + spriteBatch.Draw(texture, drawPos, source, Color.White); + break; + } + case Terrain.Block: { + Rectangle source = new Rectangle(6 * size, 3 * size, size, size); + spriteBatch.Draw(texture, drawPos, source, Color.White); + break; + } + case Terrain.Empty: + default: + break; + } + } + } + + class World { + + public const int TileSize = 16; + int width; + int height; + Tile[,] tiles; + + public int Width { get; } + public int Height { get; } + + string worldString = @" + + + + + X + . + X <======> <==X X <=> XX . + XXX . + XXXX . + XXXXX . + X <> <> = <> X X X X <> X X XX X <=X> XXXXXX . + <> [] [] XX XX XXX XX XXXXXXX . + <> [] [] [] XXX XXX XXXX XXX <> <> XXXXXXXX + []12345678[]123456[]123456789[]1234567890 123456 123456 12345 1234 12345 1234 123XXXX XXXX1234XXXXX XXXX1234[]123 1234567[]XXXXXXXXX12345678 +===========================..========..======..=========..=========> <=============> <==============================================================> <=======..==============..============================== +...................................................................] [.............] [..............................................................] [......................................................."; + + public World(Texture2D texture) { + string[] worldDesc = worldString.Split('\n'); + width = worldDesc.AsQueryable().Max(a => a.Length); + height = worldDesc.Length; + Debug.WriteLine("world size: {0}x{1}", width, height); + tiles = new Tile[width, height]; + for (int j = 0; j < height; j++) { + for (int i = 0; i < width; i++) { + Terrain terrain = Terrain.Empty; + if (i < worldDesc[j].Length) { + switch (worldDesc[j][i]) { + case '=': + terrain = Terrain.Grass; + break; + case '<': + terrain = Terrain.GrassL; + break; + case '>': + terrain = Terrain.GrassR; + break; + case '.': + terrain = Terrain.Rock; + break; + case '[': + terrain = Terrain.RockL; + break; + case ']': + terrain = Terrain.RockR; + break; + case '~': + terrain = Terrain.Water; + break; + case 'X': + terrain = Terrain.Block; + break; + case ' ': + default: + terrain = Terrain.Empty; + break; + } + } + var position = new Rectangle(i * TileSize, j * TileSize, TileSize, TileSize); + tiles[i, j] = new Tile(texture, terrain, position); + } + } + } + + public void Draw(SpriteBatch spriteBatch, Camera camera) { + for (int j = 0; j < height; j++) { + for (int i = 0; i < width; i++) { + tiles[i, j].Draw(spriteBatch, camera); + } + } + } + + public List CollisionTargets() { + var result = new List(); + for (int j = 0; j < height; j++) { + for (int i = 0; i < width; i++) { + var t = tiles[i, j]; + if (t.Terrain != Terrain.Empty) { + result.Add(t.Position); + } + } + } + return result; + } + } +} diff --git a/SharedTests/HistoryTests.cs b/SharedTests/HistoryTests.cs new file mode 100644 index 0000000..caf5cec --- /dev/null +++ b/SharedTests/HistoryTests.cs @@ -0,0 +1,48 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; + +namespace SemiColinGames.Tests { + [TestClass] + public class HistoryTests { + [TestMethod] + public void TestLength() { + var h = new History(3); + Assert.AreEqual(3, h.Length); + } + + [TestMethod] + public void TestGetFromEmpty() { + var ints = new History(3); + Assert.AreEqual(0, ints[0]); + Assert.AreEqual(0, ints[1]); + Assert.AreEqual(0, ints[2]); + + var objects = new History(1); + Assert.AreEqual(null, objects[0]); + } + + [TestMethod] + public void TestAdds() { + var h = new History(3); + Assert.AreEqual("0 0 0", String.Join(" ", h.ToArray())); + h.Add(2); + Assert.AreEqual("2 0 0", String.Join(" ", h.ToArray())); + h.Add(3); + h.Add(5); + Assert.AreEqual("5 3 2", String.Join(" ", h.ToArray())); + h.Add(7); + Assert.AreEqual("7 5 3", String.Join(" ", h.ToArray())); + h.Add(11); + h.Add(13); + Assert.AreEqual("13 11 7", String.Join(" ", h.ToArray())); + } + + [TestMethod] + public void TestThrowsExceptions() { + var h = new History(3); + Assert.ThrowsException(() => h[-1]); + Assert.ThrowsException(() => h[3]); + } + } +} diff --git a/SharedTests/Properties/AssemblyInfo.cs b/SharedTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..b93fdbf --- /dev/null +++ b/SharedTests/Properties/AssemblyInfo.cs @@ -0,0 +1,20 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("SharedTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("SharedTests")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: Guid("c86694a5-dd99-4421-aa2c-1230f11c10f8")] + +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/SharedTests/SharedTests.csproj b/SharedTests/SharedTests.csproj new file mode 100644 index 0000000..741829e --- /dev/null +++ b/SharedTests/SharedTests.csproj @@ -0,0 +1,72 @@ + + + + + + Debug + AnyCPU + {C86694A5-DD99-4421-AA2C-1230F11C10F8} + Library + Properties + SharedTests + SharedTests + v4.7.2 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\MSTest.TestFramework.2.0.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll + + + ..\packages\MSTest.TestFramework.2.0.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + + + False + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + \ No newline at end of file diff --git a/SharedTests/packages.config b/SharedTests/packages.config new file mode 100644 index 0000000..c7d3707 --- /dev/null +++ b/SharedTests/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/tools/LICENSE.txt b/tools/LICENSE.txt new file mode 100644 index 0000000..74ce39c --- /dev/null +++ b/tools/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Colin McMillen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/tools/copybara/copy.bara.sky b/tools/copybara/copy.bara.sky new file mode 100644 index 0000000..3159331 --- /dev/null +++ b/tools/copybara/copy.bara.sky @@ -0,0 +1,25 @@ +sourceUrl = "git@github.com:mcmillen/sneak.git" +destinationUrl = "git@github.com:mcmillen/sneak-public.git" + +core.workflow( + name = "default", + mode = "ITERATIVE", + origin = git.origin( + url = sourceUrl, + ref = "master", + ), + destination = git.destination( + url = destinationUrl, + fetch = "master", + push = "master", + ), + # Change path to the folder you want to publish publicly + origin_files = glob(["README.md", "tools/**", "Shared/**", "SharedTests/**", "Jumpy.Shared/**"]), + + authoring = authoring.pass_thru("Unknown Author "), + + # Change the path here to the folder you want to publish publicly + #transformations = [ + # core.move("", ""), + #], +) diff --git a/tools/copybara/copybara.sh b/tools/copybara/copybara.sh new file mode 100644 index 0000000..c9abad1 --- /dev/null +++ b/tools/copybara/copybara.sh @@ -0,0 +1,6 @@ +#/bin/sh + +SCRIPT=$(readlink -f "$0") +SCRIPTPATH=$(dirname "$SCRIPT") + +java -jar ~/bin/copybara_deploy.jar migrate $SCRIPTPATH/copy.bara.sky