HelirinState ClearLifeDataOfState(HelirinState st) { st = st.ShallowCopy(); st.life = 0; st.invul = 0; return(st); }
void ObjectHitReact(HelirinState st, uint collision_mask, short px, short py, int objx, int objy) { short dir = math.atan2(px - objx, py - objy); if ((collision_mask & middle_mask) != 0) { st.xb = math.sin(moving_object_bump_speed, dir); st.yb = -math.cos(moving_object_bump_speed, dir); st.xpos += st.xb; st.ypos += st.yb; } if ((collision_mask & (up_mask | down_mask)) != 0) { dir -= (short)((st.rot >> 8) << 8); if ((collision_mask & up_mask) != 0) { dir = (short)(dir + 0x8000); } if (dir >= 0) { st.rot_rate = -rot_bump_rate; } else { st.rot_rate = rot_bump_rate; } } }
public string[] DownloadInputs(out HelirinState hs) { hs = null; string in_path = this.in_path.Replace("@", "2"); string out_path = this.out_path.Replace("@", "2"); try { if (File.Exists(in_path)) { return(null); } if (File.Exists(out_path)) { File.Delete(out_path); } File.WriteAllText(in_path, "STARTRECORD"); while (!File.Exists(out_path)) { Thread.Sleep(250); } string[] res = File.ReadAllLines(out_path); hs = parse_hs(res[0]); string[] res2 = new string[res.Length - 1]; Array.Copy(res, 1, res2, 0, res2.Length); return(res2); } catch { } return(null); }
private void showPosition_Click(object sender, EventArgs e) { if (com != null) { hs = com.GetHelirin(); mapc.SetHelirin(hs); } }
public StateData(HelirinState es, float w, float c, Action?a, HelirinState ps, bool at) { exact_state = es; weight = w; cost = c; action = a; previous_state = ps; already_treated = at; }
public static MapControl.GraphicalHelirin ToGraphicalHelirin(HelirinState h) { if (h == null) { return(new MapControl.GraphicalHelirin()); } float angle = rot_to_angle_approx(h.rot); int xpix = pos_to_px(h.xpos); int ypix = pos_to_px(h.ypos); return(new MapControl.GraphicalHelirin(xpix, ypix, angle, h.HasBonus(), h.frameNumber)); }
private void loadPos_Click(object sender, EventArgs e) { try { if (loadFileDialog.ShowDialog() == DialogResult.OK) { hs = Com.parse_hs(File.ReadAllLines(loadFileDialog.FileName)[0]); mapc.SetHelirin(hs); } } catch { } }
public void SetHelirin(HelirinState st) { if (st != null) { helirin = Physics.ToGraphicalHelirin(st); } else { helirin = null; } Refresh(); }
private void downloadInputs_Click(object sender, EventArgs e) { if (com == null) { return; } HelirinState hs = null; string[] inputs = com.DownloadInputs(out hs); this.hs = hs; mapc.SetHelirin(hs); ExecuteInputsStr(inputs); }
public HelirinState[] Play(HelirinState hs, Action[] actions) { string in_path = this.in_path.Replace("@", "2"); string out_path = this.out_path.Replace("@", "2"); try { if (File.Exists(in_path)) { return(null); } if (File.Exists(out_path)) { File.Delete(out_path); } string txt = "PLAY\n"; txt += hs_to_string(hs); foreach (Action a in actions) { string str = Controller.effect_to_string(Controller.action_to_effect(a)); if (String.IsNullOrEmpty(str)) { str = "_"; // No empty line because they are ignored by the LUA script } txt += str + "\n"; } File.WriteAllText(in_path, txt); while (!File.Exists(out_path)) { Thread.Sleep(250); } // Parsing the file string[] res = File.ReadAllLines(out_path); List <HelirinState> hs_res = new List <HelirinState>(); try { foreach (string line in res) { if (!String.IsNullOrEmpty(line)) { hs_res.Add(parse_hs(line)); } } } catch { } return(hs_res.ToArray()); } catch { } return(null); }
HelirinState NormaliseState(HelirinState st) { st = st.ShallowCopy(); short px = Physics.pos_to_px(st.xpos); short py = Physics.pos_to_px(st.ypos); int pos_reduction = Settings.pos_reduction; int bump_reduction = Settings.bump_reduction; ushort rot_precision = Settings.rot_precision; ushort rot_rate_precision = Settings.rot_rate_precision; ushort frame_nb_precision = Settings.frame_nb_precision; float additional_reduction_dist_multiplier = Settings.additional_reduction_dist_multiplier; int max_additional_reduction = Settings.max_additional_reduction; if (f.IsHealZone(px, py)) { pos_reduction = Settings.healzone_pos_reduction; bump_reduction = Settings.healzone_bump_reduction; rot_precision = Settings.healzone_rot_precision; rot_rate_precision = Settings.healzone_rot_rate_precision; frame_nb_precision = Settings.healzone_frame_nb_precision; additional_reduction_dist_multiplier = Settings.healzone_additional_reduction_dist_multiplier; max_additional_reduction = Settings.healzone_max_additional_reduction; } float wall_dist = f.DistToWall(px, py) * additional_reduction_dist_multiplier; int add_red = wall_dist == 0 ? Settings.additional_reduction_in_wall : Math.Min((int)wall_dist, max_additional_reduction); pos_reduction += add_red; bump_reduction += add_red; st.xpos = (st.xpos >> pos_reduction) << pos_reduction; st.ypos = (st.ypos >> pos_reduction) << pos_reduction; st.xb = (st.xb >> bump_reduction) << bump_reduction; st.yb = (st.yb >> bump_reduction) << bump_reduction; st.rot = (short)((int)Math.Round((float)st.rot / rot_precision) * rot_precision); st.rot_rate = (short)((int)Math.Round((float)st.rot_rate / rot_rate_precision) * rot_rate_precision); if (!Settings.bonus_required && !st.IsTerminal()) { st.gs = GameState.InGame; } st.frameNumber = (ushort)((st.frameNumber / frame_nb_precision) * frame_nb_precision); st.invul = (sbyte)(((st.invul - 1) / Settings.invul_precision + 1) * Settings.invul_precision); return(st); }
public static string hs_to_string(HelirinState hs) { string xpos_str = hs.xpos.ToString(); string ypos_str = hs.ypos.ToString(); string xb_str = hs.xb.ToString(); string yb_str = hs.yb.ToString(); string rot_str = hs.rot.ToString(); string rot_rate_str = hs.rot_rate.ToString(); string rot_srate_str = hs.rot_srate.ToString(); string life_str = hs.life.ToString(); string invul_str = hs.invul.ToString(); string frame_number_str = hs.frameNumber.ToString(); return(xpos_str + " " + ypos_str + " " + xb_str + " " + yb_str + " " + rot_str + " " + rot_rate_str + " " + rot_srate_str + " " + life_str + " " + invul_str + " " + (hs.HasBonus() ? "1" : "0") + " " + frame_number_str + "\n"); }
private void ExecuteInputs(Action[] inputs) { if (inputs == null || hs == null || phy == null) { return; } List <Action> last_inputs = new List <Action>(); List <HelirinState> last_positions = new List <HelirinState>(); last_positions.Add(hs); foreach (Action input in inputs) { hs = phy.Next(hs, input); last_inputs.Add(input); last_positions.Add(hs); } this.last_inputs = last_inputs.ToArray(); this.last_positions = last_positions.ToArray(); last_positions_emu = null; mapc.SetHelirin(hs); }
public Action[] Solve(HelirinState init, int min_life_score) { if (cost_maps == null || init == null) { return(null); } SimplePriorityQueue <HelirinState> q = new SimplePriorityQueue <HelirinState>(); Dictionary <HelirinState, StateData> data = new Dictionary <HelirinState, StateData>(); Dictionary <HelirinState, int> life_data = null; if (!Settings.allow_state_visit_with_less_life && Settings.invul_frames >= 0 && Settings.full_life >= 2) { life_data = new Dictionary <HelirinState, int>(); } // Set range of possible inputs int min_input = 0; if (Settings.min_ab_speed == 1) { min_input = 1; } else if (Settings.min_ab_speed == 2) { min_input = 9; } else if (Settings.min_ab_speed == 3) { min_input = 17; } else if (Settings.min_ab_speed > 3) { min_input = 25; } // Init HelirinState norm_init = NormaliseState(init); float cost = GetCost(init.xpos, init.ypos, init.life, init.invul, init.HasBonus()); float weight = 0; float total_cost = cost + weight; q.Enqueue(norm_init, total_cost); data.Add(norm_init, new StateData(init, weight, cost, null, null, false)); if (life_data != null) { life_data.Add(ClearLifeDataOfState(norm_init), Flooding.GetRealInvul(init.life, init.invul)); } // ProgressBar and preview settings float init_cost = cost; bool[,] preview = new bool[cost_maps[0][0].Get(true).Height, cost_maps[0][0].Get(true).Width]; int since_last_update = 0; // A* HelirinState result = null; while (q.Count > 0 && result == null) { HelirinState norm_st = q.Dequeue(); StateData st_data = data[norm_st]; st_data.already_treated = true; weight = st_data.weight + 1; // ProgressBar and preview settings preview[Physics.pos_to_px(st_data.exact_state.ypos) - f.PixelStart.y, Physics.pos_to_px(st_data.exact_state.xpos) - f.PixelStart.x] = true; since_last_update++; if (since_last_update >= Settings.nb_iterations_before_ui_update) { since_last_update = 0; parent.UpdateProgressBarAndHighlight(100 - st_data.cost * 100 / init_cost, preview); } for (int i = 24; i >= min_input; i--) { Action a = (Action)i; HelirinState nst = p.Next(st_data.exact_state, a); HelirinState norm_nst = NormaliseState(nst); // Lose / Not enough life / Out of search space ? int life_score = Flooding.GetRealInvul(nst.life, nst.invul); if (nst.gs == GameState.Lose || (life_score < min_life_score && life_score >= 0) || IsOutOfSearchSpace(nst.xpos, nst.ypos)) { continue; } // Already enqueued with more life ? HelirinState cleared_nst = null; if (life_data != null) { cleared_nst = ClearLifeDataOfState(norm_nst); int old_life_score; life_data.TryGetValue(cleared_nst, out old_life_score); // Default value for 'old_life_score' (type int) is 0. if (old_life_score > life_score) { continue; } } // Already visited ? // If the state was already visited, we should not add it to the queue again! Otherwise it could overwrite the state entry and corrupt some paths. StateData old; data.TryGetValue(norm_nst, out old); // Default value for 'old' (type StateData) is null. if (old != null && old.already_treated) { continue; } // Keep only if it is a non-infinite better cost cost = GetCost(nst.xpos, nst.ypos, nst.life, nst.invul, nst.HasBonus()); if (cost >= float.PositiveInfinity) { continue; } total_cost = cost + weight; if (old != null && total_cost >= old.cost + old.weight) { continue; } // Is the state terminal without having completed the objective? if (cost > 0 && nst.IsTerminal()) { continue; } // Everything's okay, we add the config to the data and queue StateData nst_data = new StateData(nst, weight, cost, a, norm_st, false); data[norm_nst] = nst_data; if (life_data != null) { life_data[cleared_nst] = life_score; } // Target reached ? We look at the cost rather than the game state, because the target can be different than winning if (cost <= 0) { result = norm_nst; break; } // We don't use UpdatePriority because it does not change the InsertionIndex (first-in, first-out) if (old != null) { q.Remove(norm_nst); } q.Enqueue(norm_nst, total_cost); /* * if (old == null) * q.Enqueue(norm_nst, total_cost); * else * q.UpdatePriority(norm_nst, total_cost); */ } } // Retrieve full path if (result == null) { return(null); } List <Action> res = new List <Action>(); while (result != null) { StateData sd = data[result]; if (sd.action.HasValue) { res.Add(sd.action.Value); } result = sd.previous_state; } res.Reverse(); return(res.ToArray()); }
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); }
public void SetHelirinState(HelirinState st) { this.UIThread(() => { hs = st; mapc.SetHelirin(hs); }); }
private void playMove_KeyDown(object sender, KeyEventArgs e) { if (!controls_enabled) { return; } if (e.KeyCode >= Keys.NumPad0 && e.KeyCode <= Keys.NumPad9) { e.Handled = true; Action a = Action.NoButton; switch (e.KeyCode) { case Keys.NumPad1: a = Action.DownLeft1; break; case Keys.NumPad2: a = Action.Down1; break; case Keys.NumPad3: a = Action.DownRight1; break; case Keys.NumPad4: a = Action.Left1; break; case Keys.NumPad6: a = Action.Right1; break; case Keys.NumPad7: a = Action.UpLeft1; break; case Keys.NumPad8: a = Action.Up1; break; case Keys.NumPad9: a = Action.UpRight1; break; } Speed speed = Speed.Speed0; if (ModifierKeys.HasFlag(Keys.Alt)) { speed = Speed.Speed2; } else if (ModifierKeys.HasFlag(Keys.Control)) { speed = Speed.Speed1; } a = Controller.change_action_speed(a, speed); if (hs != null && phy != null) { hs = phy.Next(hs, a); mapc.SetHelirin(hs); } } }
private void restorePos_Click(object sender, EventArgs e) { hs = hs_bkp; mapc.SetHelirin(hs); }
private void bkpPos_Click(object sender, EventArgs e) { hs_bkp = hs; }