public HelirinState Next(HelirinState st, Action a) { if (st == null) { return(null); } if (st.IsTerminal()) { return(st); } ActionEffect e = Controller.action_to_effect(a); st = st.ShallowCopy(); st.frameNumber += 1; if (st.life > Settings.full_life) { st.life = Settings.full_life; } if (st.invul > Settings.invul_frames) { st.invul = Settings.invul_frames; } // 1. Set input speed (XS and YS) depending on inputs int[] speeds = input_speeds; if (e.x != Direction1.None && e.y != Direction1.None) { speeds = input_speeds_2; } int speed = speeds[(int)e.speed]; int xs = (int)e.x * speed; int ys = (int)e.y * speed; // 2. Reduce bump speed / bump rotation (XB, YB and Rot_rate) / invulnerability short rot_diff = (short)(st.rot_srate - st.rot_rate); if (rot_diff < -rotation_rate_decr) { rot_diff = -rotation_rate_decr; } if (rot_diff > rotation_rate_decr) { rot_diff = rotation_rate_decr; } st.rot_rate = (short)(st.rot_rate + rot_diff); st.xb = st.xb * bump_speed_decrease_numerator / bump_speed_decrease_denominator; st.yb = st.yb * bump_speed_decrease_numerator / bump_speed_decrease_denominator; if (st.invul > 0) { st.invul--; } // 3. Move depending on speed (bump+input), rotate depending on rotation rate (Rot_rate) st.xpos += xs + st.xb; st.ypos += ys + st.yb; st.rot += st.rot_rate; // 4. Detection of healing/ending zones // Position seems to be converted to px with a shift: subpixels seem to be ignored even in negative positions. bool safe_zone = false; Map.Zone zone = map.IsPixelInZone(pos_to_px(st.xpos), pos_to_px(st.ypos)); if (zone == Map.Zone.Healing || zone == Map.Zone.Starting) { safe_zone = true; if (st.life < Settings.full_life) { st.life = Settings.full_life; } } if (zone == Map.Zone.Ending) { st.gs = st.gs == GameState.InGameWithBonus ? GameState.WinWithBonus : GameState.Win; return(st); } // At this point, we backup the rotation data (will be needed later) short rot_rate_bkp = st.rot_rate; // No need to backup st.rot because it will not change anymore // We also precompute all the helirin physical points and the collision mask // TODO: Optionally, use memoisation to avoid recomputing collision mask each time short[] pxs = new short[helirin_points.Length]; short[] pys = new short[helirin_points.Length]; uint collision_mask = 0; for (int i = 0; i < helirin_points.Length; i++) { int radius = helirin_points[i]; // Position seems to be converted to px BEFORE adding the result of the sin/cos (it seems to ignore subpixels, even in negative positions). short px = (short)(pos_to_px(st.xpos) - math.sin(radius, st.rot)); short py = (short)(pos_to_px(st.ypos) + math.cos(radius, st.rot)); pxs[i] = px; pys[i] = py; // Compute collision mask if (map.IsPixelInCollision(px, py)) { collision_mask |= ((uint)1 << i); } } // 5. Action of springs & bonus HashSet <int> spring_already_visited = new HashSet <int>(); bool invert_rotation = false; bool update_rot_rate = false; foreach (int i in helirin_points_order_for_springs) // Order is important for spring actions. { int radius = helirin_points[i]; short px = pxs[i]; short py = pys[i]; // Action of springs Map.Spring[] springs = map.IsPixelInSpring(px, py); foreach (Map.Spring spr in springs) { if (!spring_already_visited.Contains(spr.unique_id)) { spring_already_visited.Add(spr.unique_id); if (radius != 0) { update_rot_rate = true; // Invert rotation if at least one spring is in the right direction if (!invert_rotation) { short spring_angle = AngleOfSpring(spr.type); short helirin_angle = radius > 0 ? st.rot : (short)(st.rot + (0x10000 / 2)); short helirin_normal_angle = (short)(helirin_angle + Math.Sign(st.rot_srate) * (0x10000 / 4)); short diff = (short)(spring_angle - helirin_normal_angle); if (Math.Abs((int)diff) > 0x10000 / 4) { invert_rotation = true; } } } // Position bump if (spr.type == Map.SpringType.Up) { st.xb = 0; st.yb = -bump_speed_spring; } if (spr.type == Map.SpringType.Down) { st.xb = 0; st.yb = bump_speed_spring; } if (spr.type == Map.SpringType.Left) { st.xb = -bump_speed_spring; st.yb = 0; } if (spr.type == Map.SpringType.Right) { st.xb = bump_speed_spring; st.yb = 0; } st.xpos += st.xb; st.ypos += st.yb; } } // Action of bonus if (map.IsPixelInBonus(px, py) != Map.BonusType.None) { st.gs = GameState.InGameWithBonus; } } if (invert_rotation) { st.rot_srate = (short)(-st.rot_srate); } if (update_rot_rate) { st.rot_rate = (short)(Math.Sign(st.rot_srate) * rot_bump_rate_spring); if (st.rot_rate == 0) { st.rot_rate = -rot_bump_rate_spring; } } // 6. Action of moving objects uint object_collision_mask = 0; if (Settings.enable_moving_objects) { List <Roller.Ball> balls = new List <Roller.Ball>(); foreach (Roller r in map.Rollers) { Roller.Ball ball = r.PreciseBoxAtTime(st.frameNumber); if (ball != null) { balls.Add(ball); } } balls.Sort((x, y) => x < y ? -1 : (x > y ? 1 : 0)); foreach (Roller.Ball ball in balls) { uint elt_collision_mask = 0; for (int i = 0; i < helirin_points.Length; i++) { // For rollers, position of physical points must be recomputed to take into account last position/rotation changes // EDIT: Seems not... At least, spring actions should not affect it /*int radius = helirin_points[i]; * short px = (short)(pos_to_px(st.xpos) - math.sin(radius, st.rot)); * short py = (short)(pos_to_px(st.ypos) + math.cos(radius, st.rot));*/ short px = pxs[i]; short py = pys[i]; if (ball.InCollisionWith(px, py)) { elt_collision_mask |= ((uint)1 << i); } } if (elt_collision_mask != 0) { object_collision_mask |= elt_collision_mask; ObjectHitReact(st, elt_collision_mask, /*pxs[0], pys[0],*/ pos_to_px(st.xpos), pos_to_px(st.ypos), ball.cx, ball.cy); } } foreach (Piston p in map.Pistons) { Rectangle?box = null; uint elt_collision_mask = 0; for (int i = 0; i < helirin_points.Length; i++) { short px = pxs[i]; short py = pys[i]; if (p.dangerArea.Contains(px, py)) { if (box == null) { box = p.PreciseBoxAtTime(st.frameNumber); } if (box.Value.Contains(px, py)) { elt_collision_mask |= ((uint)1 << i); } } } if (elt_collision_mask != 0) { object_collision_mask |= elt_collision_mask; ObjectHitReact(st, elt_collision_mask, /*pxs[0], pys[0],*/ pos_to_px(st.xpos), pos_to_px(st.ypos), box.Value.X + (box.Value.Width - 1) / 2, box.Value.Y + (box.Value.Height - 1) / 2); } } } if (collision_mask != 0 || object_collision_mask != 0) // If collision with a wall OR a moving object { // 7. Damage and substract input speed (XS and YS) to position if (!safe_zone) { if (st.invul == 0) { st.invul = Settings.invul_frames; st.life--; } } st.xpos -= xs; st.ypos -= ys; if (collision_mask != 0) // If collision with a wall { // 8. Bump action // - Modify bump speed and rot rate (XB, YB and Rot_rate) accordingly if relevant // - If modified, apply this newly computed bump speed to position bool up_side = (collision_mask & up_mask) != 0; bool down_side = (collision_mask & down_mask) != 0; if (up_side && !down_side) { st.xb = -math.sin(auto_bump_speed, st.rot); st.yb = math.cos(auto_bump_speed, st.rot); } if (!up_side && down_side) { st.xb = math.sin(auto_bump_speed, st.rot); st.yb = -math.cos(auto_bump_speed, st.rot); } st.rot_rate = (short)(-Math.Sign(rot_rate_bkp) * rot_bump_rate); if (st.rot_rate == 0) { st.rot_rate = rot_bump_rate; } if (up_side != down_side) { st.xpos += st.xb; st.ypos += st.yb; } // 9. If mask has collision at one of the 3 lowest bits : // - Modify bump speed (XB and YB) depending on input (if any) // - If modified, apply this newly computed bump speed to position if ((collision_mask & middle_mask) != 0 && (e.x != Direction1.None || e.y != Direction1.None)) { int bump_speed = input_bump_speed; if (e.x != Direction1.None && e.y != Direction1.None) { bump_speed = input_bump_speed_2; } st.xb = -(int)e.x * bump_speed; st.yb = -(int)e.y * bump_speed; st.xpos += st.xb; st.ypos += st.yb; } } } // Lose? // TODO: Move up so that some useless computation can be avoided when losing if (st.life == 0) { st.gs = GameState.Lose; } return(st); }
protected void Redraw() { if (m != null) { Bitmap bitmap; int width = showP ? 3 * m.WidthPx : m.WidthPx; int height = showP ? 3 * m.HeightPx : m.HeightPx; int start_x = showP ? -m.WidthPx : 0; int start_y = showP ? -m.HeightPx : 0; if (start_pixel.HasValue) { start_x = start_pixel.Value.x; start_y = start_pixel.Value.y; } if (start_pixel.HasValue && end_pixel.HasValue) { width = end_pixel.Value.x - start_pixel.Value.x + 1; height = end_pixel.Value.y - start_pixel.Value.y + 1; } if (showC && cost_map != null) { width = Math.Min(width, cost_map.Width); height = Math.Min(height, cost_map.Height); } bitmap = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); computed_start_pixel = new Pixel((short)start_x, (short)start_y); Graphics g = Graphics.FromImage(bitmap); g.Clear(BackColor); Brush startingBrush = new SolidBrush(Color.FromArgb(0x77, 0xD0, 0x00, 0x00)); Brush healingBrush = new SolidBrush(Color.FromArgb(0x77, 0x40, 0x40, 0xFF)); Brush endingBrush = new SolidBrush(Color.FromArgb(0x77, 0xD0, 0xD0, 0x00)); Brush springBrush = new SolidBrush(Color.FromArgb(0x77, 0x80, 0x80, 0x80)); Brush collisionBrush = Brushes.SlateGray; Brush phyStartingBrush = new SolidBrush(Color.FromArgb(0x55, 0xD0, 0x00, 0x00)); Brush phyHealingBrush = new SolidBrush(Color.FromArgb(0x55, 0x40, 0x40, 0xFF)); Brush phyEndingBrush = new SolidBrush(Color.FromArgb(0x55, 0xD0, 0xD0, 0x00)); Brush phySpringBrush = new SolidBrush(Color.FromArgb(0x55, 0x80, 0x80, 0x80)); if (showC && cost_map != null) { float max = cost_map.GetMaxWeightExceptInfinity(cost_map_real_invul); if (max <= 0) { max = float.Epsilon; } for (int y = 0; y < bitmap.Height; y++) { for (int x = 0; x < bitmap.Width; x++) { Rectangle dest = new Rectangle(x, y, 1, 1); float cost = cost_map.CostAtIndex(x, y, cost_map_real_invul); int color = cost < float.PositiveInfinity ? (int)((cost / max) * 255) : 255; color = 255 - color; Brush brush = new SolidBrush(Color.FromArgb(255, color, color, color)); g.FillRectangle(brush, dest); } } } else { if (showP) { for (int y = 0; y < bitmap.Height; y++) { for (int x = 0; x < bitmap.Width; x++) { short xp = (short)(x + start_x); short yp = (short)(y + start_y); Rectangle dest = new Rectangle(x, y, 1, 1); if (m.IsPixelInCollision(xp, yp)) { g.FillRectangle(collisionBrush, dest); } Map.Zone zone = m.IsPixelInZone(xp, yp); if (zone == Map.Zone.Starting) { g.FillRectangle(phyStartingBrush, dest); } else if (zone == Map.Zone.Healing) { g.FillRectangle(phyHealingBrush, dest); } else if (zone == Map.Zone.Ending) { g.FillRectangle(phyEndingBrush, dest); } Map.Spring[] springs = m.IsPixelInSpring(xp, yp); foreach (Map.Spring s in springs) { g.FillRectangle(phySpringBrush, dest); } } } } if (showG) { for (int y = 0; y < bitmap.Height; y += Map.tile_size) { for (int x = 0; x < bitmap.Width; x += Map.tile_size) { int tile_x = x + start_x; int tile_y = y + start_y; if (tile_x < 0 || tile_y < 0 || tile_x >= m.WidthPx || tile_y >= m.HeightPx) { continue; } ushort tile = m.TileAt(tile_x / Map.tile_size, tile_y / Map.tile_size); int x_offset = tile_x % Map.tile_size; int y_offset = tile_y % Map.tile_size; Rectangle dest = new Rectangle(x - x_offset, y - y_offset, Map.tile_size, Map.tile_size); Rectangle?sprite = m.GetTileSprite(tile); if (sprite.HasValue) { g.DrawImage(Resources.sprites, dest, sprite.Value, GraphicsUnit.Pixel); } Map.Zone zone = m.IsTileAZone(tile); if (zone == Map.Zone.Starting) { g.FillRectangle(startingBrush, dest); } else if (zone == Map.Zone.Healing) { g.FillRectangle(healingBrush, dest); } else if (zone == Map.Zone.Ending) { g.FillRectangle(endingBrush, dest); } if (m.IsTileASpring(tile).HasValue) { dest = new Rectangle(dest.X, dest.Y, Map.spring_size, Map.spring_size); g.FillRectangle(springBrush, dest); } } } } } if (showO) { void drawRectangle(Rectangle r, Brush brush) { g.FillRectangle(brush, r.X - start_x, r.Y - start_y, r.Width, r.Height); } Brush b = new SolidBrush(Color.FromArgb(0x44, 0xAA, 0x22, 0x77)); foreach (Piston p in m.Pistons) { drawRectangle(p.dangerArea, b); } b = new SolidBrush(Color.FromArgb(0x44, 0xAA, 0x77, 0x22)); foreach (Roller r in m.Rollers) { drawRectangle(r.dangerArea, b); } } bmap = bitmap; } else { bmap = null; } Refresh(); }
public bool IsHealZone(short x, short y) { Map.Zone zone = m.IsPixelInZone(x, y); return(zone == Map.Zone.Healing || zone == Map.Zone.Starting); }